refactor(plugin): rename plugin files and update command structure
This commit is contained in:
parent
a93cc3540a
commit
778ce64e4b
71 changed files with 1130 additions and 1871 deletions
|
|
@ -1,664 +0,0 @@
|
||||||
---
|
|
||||||
name: core
|
|
||||||
description: Use when working in host-uk repositories, running tests, building, releasing, or managing multi-repo workflows. Provides the core CLI command reference.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Core CLI
|
|
||||||
|
|
||||||
The `core` command provides a unified interface for Go/Wails development, multi-repo management, and deployment.
|
|
||||||
|
|
||||||
**Rule:** Always prefer `core <command>` over raw commands. It handles environment setup, output formatting, and cross-platform concerns.
|
|
||||||
|
|
||||||
## Command Quick Reference
|
|
||||||
|
|
||||||
| Task | Command | Notes |
|
|
||||||
|------|---------|-------|
|
|
||||||
| Run Go tests | `core go test` | Sets macOS deployment target, filters warnings |
|
|
||||||
| Run Go tests with coverage | `core go cov` | HTML report, thresholds |
|
|
||||||
| Format Go code | `core go fmt --fix` | Uses goimports/gofmt |
|
|
||||||
| Lint Go code | `core go lint` | Uses golangci-lint |
|
|
||||||
| Tidy Go modules | `core go mod tidy` | go mod tidy wrapper |
|
|
||||||
| Sync Go workspace | `core go work sync` | go work sync wrapper |
|
|
||||||
| Install Go binary | `core go install` | Auto-detects cmd/ |
|
|
||||||
| Run PHP tests | `core php test` | Auto-detects Pest/PHPUnit |
|
|
||||||
| Start PHP dev server | `core php dev` | FrankenPHP + Vite + Horizon + Reverb |
|
|
||||||
| Format PHP code | `core php fmt --fix` | Laravel Pint |
|
|
||||||
| Deploy PHP app | `core php deploy` | Coolify deployment |
|
|
||||||
| Build project | `core build` | Auto-detects project type |
|
|
||||||
| Build for targets | `core build --targets linux/amd64,darwin/arm64` | Cross-compile |
|
|
||||||
| Build SDK | `core build sdk` | Generate API clients from OpenAPI |
|
|
||||||
| Preview release | `core ci` | Dry-run publish (safe default) |
|
|
||||||
| Publish release | `core ci --we-are-go-for-launch` | Actually publish artifacts |
|
|
||||||
| Check environment | `core doctor` | Verify tools installed |
|
|
||||||
| Multi-repo status | `core dev health` | Quick summary across repos |
|
|
||||||
| Multi-repo workflow | `core dev work` | Status + commit + push |
|
|
||||||
| Commit dirty repos | `core dev commit` | Claude-assisted commit messages |
|
|
||||||
| Push repos | `core dev push` | Push repos with unpushed commits |
|
|
||||||
| Pull repos | `core dev pull` | Pull repos that are behind |
|
|
||||||
| List issues | `core dev issues` | Open issues across repos |
|
|
||||||
| List PRs | `core dev reviews` | PRs needing review |
|
|
||||||
| Check CI | `core dev ci` | GitHub Actions status |
|
|
||||||
| Validate OpenAPI | `core sdk validate` | Validate OpenAPI spec |
|
|
||||||
| Check API changes | `core sdk diff` | Detect breaking API changes |
|
|
||||||
| Sync docs | `core docs sync` | Sync docs across repos |
|
|
||||||
| Search packages | `core pkg search <query>` | GitHub search for core-* repos |
|
|
||||||
| Install package | `core pkg install <name>` | Clone and register package |
|
|
||||||
| Update packages | `core pkg update` | Pull latest for all packages |
|
|
||||||
| Run VM | `core vm run <image>` | Run LinuxKit VM |
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
**Always use `core build` instead of `go build`.**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Auto-detect and build
|
|
||||||
core build
|
|
||||||
|
|
||||||
# Build for specific targets
|
|
||||||
core build --targets linux/amd64,darwin/arm64
|
|
||||||
|
|
||||||
# Build Docker image
|
|
||||||
core build --type docker
|
|
||||||
|
|
||||||
# Build LinuxKit image
|
|
||||||
core build --type linuxkit --format qcow2-bios
|
|
||||||
|
|
||||||
# CI mode (JSON output)
|
|
||||||
core build --ci
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Handles cross-compilation, code signing, archiving, checksums, and CI output formatting.
|
|
||||||
|
|
||||||
## Releasing
|
|
||||||
|
|
||||||
Build and publish are **separated** to prevent accidental releases:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Step 1: Build artifacts (safe - no publishing)
|
|
||||||
core build
|
|
||||||
core build sdk
|
|
||||||
|
|
||||||
# Step 2: Preview publish (default is dry-run)
|
|
||||||
core ci # Dry-run: shows what would be published
|
|
||||||
|
|
||||||
# Step 3: Actually publish (explicit flag required)
|
|
||||||
core ci --we-are-go-for-launch # Actually publish to targets
|
|
||||||
core ci --we-are-go-for-launch --draft # Publish as draft
|
|
||||||
core ci --we-are-go-for-launch --prerelease # Publish as prerelease
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why safe by default?** `core ci` always does a dry-run unless you explicitly say `--we-are-go-for-launch`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Release workflow utilities
|
|
||||||
core ci init # Initialize .core/release.yaml
|
|
||||||
core ci changelog # Generate changelog from commits
|
|
||||||
core ci version # Show determined version
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multi-Repo Workflow
|
|
||||||
|
|
||||||
When working across host-uk repositories:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Quick health check
|
|
||||||
core dev health
|
|
||||||
# Output: "18 repos │ clean │ synced"
|
|
||||||
|
|
||||||
# Full status table
|
|
||||||
core dev work --status
|
|
||||||
|
|
||||||
# Commit + push workflow
|
|
||||||
core dev work
|
|
||||||
|
|
||||||
# Commit dirty repos with Claude
|
|
||||||
core dev commit
|
|
||||||
|
|
||||||
# Push repos with unpushed commits
|
|
||||||
core dev push
|
|
||||||
|
|
||||||
# Pull repos that are behind
|
|
||||||
core dev pull
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dependency Analysis
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# What depends on core-php?
|
|
||||||
core dev impact core-php
|
|
||||||
```
|
|
||||||
|
|
||||||
## GitHub Integration
|
|
||||||
|
|
||||||
Requires `gh` CLI authenticated.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Open issues across all repos
|
|
||||||
core dev issues
|
|
||||||
|
|
||||||
# Include closed issues
|
|
||||||
core dev issues --all
|
|
||||||
|
|
||||||
# PRs needing review
|
|
||||||
core dev reviews
|
|
||||||
|
|
||||||
# CI status
|
|
||||||
core dev ci
|
|
||||||
```
|
|
||||||
|
|
||||||
## SDK Generation
|
|
||||||
|
|
||||||
Generate API clients from OpenAPI specs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Generate all configured SDKs
|
|
||||||
core build sdk
|
|
||||||
|
|
||||||
# Generate specific language
|
|
||||||
core build sdk --lang typescript
|
|
||||||
core build sdk --lang php
|
|
||||||
|
|
||||||
# Specify OpenAPI spec
|
|
||||||
core build sdk --spec ./openapi.yaml
|
|
||||||
|
|
||||||
# Preview without generating
|
|
||||||
core build sdk --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
## SDK Validation
|
|
||||||
|
|
||||||
Validate specs and check for breaking changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Validate OpenAPI spec
|
|
||||||
core sdk validate
|
|
||||||
core sdk validate --spec ./api.yaml
|
|
||||||
|
|
||||||
# Check for breaking API changes
|
|
||||||
core sdk diff --base v1.0.0
|
|
||||||
core sdk diff --base ./old-api.yaml --spec ./new-api.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List docs across repos
|
|
||||||
core docs list
|
|
||||||
|
|
||||||
# Sync docs to central location
|
|
||||||
core docs sync
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check development environment
|
|
||||||
core doctor
|
|
||||||
|
|
||||||
# Clone all repos from registry
|
|
||||||
core setup
|
|
||||||
```
|
|
||||||
|
|
||||||
## Package Management
|
|
||||||
|
|
||||||
Manage host-uk/core-* packages and repositories.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Search GitHub for packages
|
|
||||||
core pkg search <query>
|
|
||||||
core pkg search core- # Find all core-* packages
|
|
||||||
core pkg search --org host-uk # Search specific org
|
|
||||||
|
|
||||||
# Install/clone a package
|
|
||||||
core pkg install core-api
|
|
||||||
core pkg install host-uk/core-api # Full name
|
|
||||||
|
|
||||||
# List installed packages
|
|
||||||
core pkg list
|
|
||||||
core pkg list --format json # JSON output
|
|
||||||
|
|
||||||
# Update installed packages
|
|
||||||
core pkg update # Update all
|
|
||||||
core pkg update core-api # Update specific package
|
|
||||||
|
|
||||||
# Check for outdated packages
|
|
||||||
core pkg outdated
|
|
||||||
```
|
|
||||||
|
|
||||||
## Go Development
|
|
||||||
|
|
||||||
**Always use `core go` commands instead of raw go commands.**
|
|
||||||
|
|
||||||
### Quick Reference
|
|
||||||
|
|
||||||
| Task | Command | Notes |
|
|
||||||
|------|---------|-------|
|
|
||||||
| Run tests | `core go test` | Filters warnings, colour output |
|
|
||||||
| Coverage report | `core go cov` | HTML report, thresholds |
|
|
||||||
| Format code | `core go fmt --fix` | Uses goimports if available |
|
|
||||||
| Lint code | `core go lint` | Uses golangci-lint |
|
|
||||||
| Install binary | `core go install` | Auto-detects cmd/, --no-cgo option |
|
|
||||||
| Tidy modules | `core go mod tidy` | go mod tidy |
|
|
||||||
| Sync workspace | `core go work sync` | go work sync |
|
|
||||||
|
|
||||||
### Installing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install current module (auto-detects cmd/ subdirs)
|
|
||||||
core go install
|
|
||||||
|
|
||||||
# Install specific path
|
|
||||||
core go install ./cmd/core
|
|
||||||
|
|
||||||
# Pure Go, no C dependencies
|
|
||||||
core go install --no-cgo
|
|
||||||
|
|
||||||
# Verbose output
|
|
||||||
core go install -v
|
|
||||||
```
|
|
||||||
|
|
||||||
### Coverage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run tests with coverage summary
|
|
||||||
core go cov
|
|
||||||
|
|
||||||
# Generate HTML report
|
|
||||||
core go cov --html
|
|
||||||
|
|
||||||
# Generate and open in browser
|
|
||||||
core go cov --open
|
|
||||||
|
|
||||||
# Fail if coverage below threshold
|
|
||||||
core go cov --threshold 80
|
|
||||||
|
|
||||||
# Specific package
|
|
||||||
core go cov --pkg ./pkg/release
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run all tests
|
|
||||||
core go test
|
|
||||||
|
|
||||||
# With coverage
|
|
||||||
core go test --coverage
|
|
||||||
|
|
||||||
# Specific package
|
|
||||||
core go test --pkg ./pkg/errors
|
|
||||||
|
|
||||||
# Run specific tests
|
|
||||||
core go test --run TestHash
|
|
||||||
|
|
||||||
# Short tests only
|
|
||||||
core go test --short
|
|
||||||
|
|
||||||
# Race detection
|
|
||||||
core go test --race
|
|
||||||
|
|
||||||
# JSON output for CI
|
|
||||||
core go test --json
|
|
||||||
|
|
||||||
# Verbose
|
|
||||||
core go test -v
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why:** Sets `CGO_ENABLED=0` and `MACOSX_DEPLOYMENT_TARGET=26.0`, filters linker warnings, provides colour-coded coverage.
|
|
||||||
|
|
||||||
### Formatting & Linting
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check formatting
|
|
||||||
core go fmt
|
|
||||||
|
|
||||||
# Fix formatting
|
|
||||||
core go fmt --fix
|
|
||||||
|
|
||||||
# Show diff
|
|
||||||
core go fmt --diff
|
|
||||||
|
|
||||||
# Run linter
|
|
||||||
core go lint
|
|
||||||
|
|
||||||
# Lint with auto-fix
|
|
||||||
core go lint --fix
|
|
||||||
```
|
|
||||||
|
|
||||||
### Module Management
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Tidy go.mod
|
|
||||||
core go mod tidy
|
|
||||||
|
|
||||||
# Download dependencies
|
|
||||||
core go mod download
|
|
||||||
|
|
||||||
# Verify dependencies
|
|
||||||
core go mod verify
|
|
||||||
|
|
||||||
# Show dependency graph
|
|
||||||
core go mod graph
|
|
||||||
```
|
|
||||||
|
|
||||||
### Workspace Management
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Sync workspace
|
|
||||||
core go work sync
|
|
||||||
|
|
||||||
# Initialize workspace
|
|
||||||
core go work init
|
|
||||||
|
|
||||||
# Add module to workspace
|
|
||||||
core go work use ./pkg/mymodule
|
|
||||||
|
|
||||||
# Auto-add all modules
|
|
||||||
core go work use
|
|
||||||
```
|
|
||||||
|
|
||||||
## PHP Development
|
|
||||||
|
|
||||||
**Always use `core php` commands instead of raw artisan/composer/phpunit.**
|
|
||||||
|
|
||||||
### Quick Reference
|
|
||||||
|
|
||||||
| Task | Command | Notes |
|
|
||||||
|------|---------|-------|
|
|
||||||
| Start dev environment | `core php dev` | FrankenPHP + Vite + Horizon + Reverb + Redis |
|
|
||||||
| Run PHP tests | `core php test` | Auto-detects Pest/PHPUnit |
|
|
||||||
| Format code | `core php fmt --fix` | Laravel Pint |
|
|
||||||
| Static analysis | `core php analyse` | PHPStan/Larastan |
|
|
||||||
| Build Docker image | `core php build` | Production-ready FrankenPHP |
|
|
||||||
| Deploy to Coolify | `core php deploy` | With status tracking |
|
|
||||||
|
|
||||||
### Development Server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start full Laravel dev environment
|
|
||||||
core php dev
|
|
||||||
|
|
||||||
# Start with HTTPS (uses mkcert)
|
|
||||||
core php dev --https
|
|
||||||
|
|
||||||
# Skip specific services
|
|
||||||
core php dev --no-vite --no-horizon
|
|
||||||
|
|
||||||
# Custom port
|
|
||||||
core php dev --port 9000
|
|
||||||
```
|
|
||||||
|
|
||||||
**Services orchestrated:**
|
|
||||||
- FrankenPHP/Octane (port 8000, HTTPS on 443)
|
|
||||||
- Vite dev server (port 5173)
|
|
||||||
- Laravel Horizon (queue workers)
|
|
||||||
- Laravel Reverb (WebSocket, port 8080)
|
|
||||||
- Redis (port 6379)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View logs
|
|
||||||
core php logs
|
|
||||||
core php logs --service frankenphp
|
|
||||||
|
|
||||||
# Check status
|
|
||||||
core php status
|
|
||||||
|
|
||||||
# Stop all services
|
|
||||||
core php stop
|
|
||||||
|
|
||||||
# Setup SSL certificates
|
|
||||||
core php ssl
|
|
||||||
core php ssl --domain myapp.test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run all tests (auto-detects Pest/PHPUnit)
|
|
||||||
core php test
|
|
||||||
|
|
||||||
# Run in parallel
|
|
||||||
core php test --parallel
|
|
||||||
|
|
||||||
# With coverage
|
|
||||||
core php test --coverage
|
|
||||||
|
|
||||||
# Filter tests
|
|
||||||
core php test --filter UserTest
|
|
||||||
core php test --group api
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Quality
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check formatting (dry-run)
|
|
||||||
core php fmt
|
|
||||||
|
|
||||||
# Auto-fix formatting
|
|
||||||
core php fmt --fix
|
|
||||||
|
|
||||||
# Show diff
|
|
||||||
core php fmt --diff
|
|
||||||
|
|
||||||
# Run static analysis
|
|
||||||
core php analyse
|
|
||||||
|
|
||||||
# Max strictness
|
|
||||||
core php analyse --level 9
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building & Deployment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build Docker image
|
|
||||||
core php build
|
|
||||||
core php build --name myapp --tag v1.0
|
|
||||||
|
|
||||||
# Build for specific platform
|
|
||||||
core php build --platform linux/amd64
|
|
||||||
|
|
||||||
# Build LinuxKit image
|
|
||||||
core php build --type linuxkit --format iso
|
|
||||||
|
|
||||||
# Run production container
|
|
||||||
core php serve --name myapp
|
|
||||||
core php serve --name myapp -d # Detached
|
|
||||||
|
|
||||||
# Open shell in container
|
|
||||||
core php shell myapp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Coolify Deployment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Deploy to production
|
|
||||||
core php deploy
|
|
||||||
|
|
||||||
# Deploy to staging
|
|
||||||
core php deploy --staging
|
|
||||||
|
|
||||||
# Wait for completion
|
|
||||||
core php deploy --wait
|
|
||||||
|
|
||||||
# Check deployment status
|
|
||||||
core php deploy:status
|
|
||||||
|
|
||||||
# List recent deployments
|
|
||||||
core php deploy:list
|
|
||||||
|
|
||||||
# Rollback
|
|
||||||
core php deploy:rollback
|
|
||||||
core php deploy:rollback --id abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
**Required .env configuration:**
|
|
||||||
```env
|
|
||||||
COOLIFY_URL=https://coolify.example.com
|
|
||||||
COOLIFY_TOKEN=your-api-token
|
|
||||||
COOLIFY_APP_ID=production-app-id
|
|
||||||
COOLIFY_STAGING_APP_ID=staging-app-id
|
|
||||||
```
|
|
||||||
|
|
||||||
### Package Management
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Link local packages for development
|
|
||||||
core php packages link ../my-package
|
|
||||||
core php packages link ../pkg-a ../pkg-b
|
|
||||||
|
|
||||||
# List linked packages
|
|
||||||
core php packages list
|
|
||||||
|
|
||||||
# Update linked packages
|
|
||||||
core php packages update
|
|
||||||
|
|
||||||
# Unlink packages
|
|
||||||
core php packages unlink vendor/my-package
|
|
||||||
```
|
|
||||||
|
|
||||||
## VM Management
|
|
||||||
|
|
||||||
LinuxKit VMs are lightweight, immutable VMs built from YAML templates.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run LinuxKit image
|
|
||||||
core vm run server.iso
|
|
||||||
|
|
||||||
# Run with options
|
|
||||||
core vm run -d --memory 2048 --cpus 4 image.iso
|
|
||||||
|
|
||||||
# Run from template
|
|
||||||
core vm run --template core-dev --var SSH_KEY="ssh-rsa AAAA..."
|
|
||||||
|
|
||||||
# List running VMs
|
|
||||||
core vm ps
|
|
||||||
core vm ps -a # Include stopped
|
|
||||||
|
|
||||||
# Stop VM
|
|
||||||
core vm stop <id>
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
core vm logs <id>
|
|
||||||
core vm logs -f <id> # Follow
|
|
||||||
|
|
||||||
# Execute command in VM
|
|
||||||
core vm exec <id> ls -la
|
|
||||||
core vm exec <id> /bin/sh
|
|
||||||
|
|
||||||
# Manage templates
|
|
||||||
core vm templates # List templates
|
|
||||||
core vm templates show <name> # Show template content
|
|
||||||
core vm templates vars <name> # Show template variables
|
|
||||||
```
|
|
||||||
|
|
||||||
## Decision Tree
|
|
||||||
|
|
||||||
```
|
|
||||||
Go project?
|
|
||||||
└── Run tests: core go test [--coverage]
|
|
||||||
└── Format: core go fmt --fix
|
|
||||||
└── Lint: core go lint
|
|
||||||
└── Tidy modules: core go mod tidy
|
|
||||||
└── Build: core build [--targets <os/arch>]
|
|
||||||
└── Build SDK: core build sdk
|
|
||||||
└── Preview publish: core ci
|
|
||||||
└── Publish: core ci --we-are-go-for-launch
|
|
||||||
|
|
||||||
PHP/Laravel project?
|
|
||||||
└── Start dev: core php dev [--https]
|
|
||||||
└── Run tests: core php test [--parallel]
|
|
||||||
└── Format: core php fmt --fix
|
|
||||||
└── Analyse: core php analyse
|
|
||||||
└── Build image: core php build
|
|
||||||
└── Deploy: core php deploy [--staging]
|
|
||||||
|
|
||||||
Working across multiple repos?
|
|
||||||
└── Quick check: core dev health
|
|
||||||
└── Full workflow: core dev work
|
|
||||||
└── Just commit: core dev commit
|
|
||||||
└── Just push: core dev push
|
|
||||||
|
|
||||||
Need GitHub info?
|
|
||||||
└── Issues: core dev issues
|
|
||||||
└── PRs: core dev reviews
|
|
||||||
└── CI: core dev ci
|
|
||||||
|
|
||||||
Setting up environment?
|
|
||||||
└── Check: core doctor
|
|
||||||
└── Clone all: core setup
|
|
||||||
|
|
||||||
Managing packages?
|
|
||||||
└── Search: core pkg search <query>
|
|
||||||
└── Install: core pkg install <name>
|
|
||||||
└── Update: core pkg update
|
|
||||||
└── Check outdated: core pkg outdated
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Mistakes
|
|
||||||
|
|
||||||
| Wrong | Right | Why |
|
|
||||||
|-------|-------|-----|
|
|
||||||
| `go test ./...` | `core go test` | CGO disabled, filters warnings, coverage |
|
|
||||||
| `go fmt ./...` | `core go fmt --fix` | Uses goimports, consistent |
|
|
||||||
| `golangci-lint run` | `core go lint` | Consistent interface |
|
|
||||||
| `go build` | `core build` | Missing cross-compile, signing, checksums |
|
|
||||||
| `php artisan serve` | `core php dev` | Missing Vite, Horizon, Reverb, Redis |
|
|
||||||
| `./vendor/bin/pest` | `core php test` | Inconsistent invocation |
|
|
||||||
| `./vendor/bin/pint` | `core php fmt --fix` | Consistent interface |
|
|
||||||
| `git status` in each repo | `core dev health` | Slow, manual |
|
|
||||||
| `gh pr list` per repo | `core dev reviews` | Aggregated view |
|
|
||||||
| Manual commits across repos | `core dev commit` | Consistent messages, Co-Authored-By |
|
|
||||||
| Manual Coolify deploys | `core php deploy` | Tracked, scriptable |
|
|
||||||
| Raw `linuxkit run` | `core vm run` | Unified interface, templates |
|
|
||||||
| `gh repo clone` | `core pkg install` | Auto-detects org, adds to registry |
|
|
||||||
| Manual GitHub search | `core pkg search` | Filtered to org, formatted output |
|
|
||||||
| `core ci` without build | `core build && core ci` | Build first, then publish |
|
|
||||||
| `core sdk generate` | `core build sdk` | SDK generation moved to build |
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Core reads from `.core/` directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
.core/
|
|
||||||
├── release.yaml # Release targets
|
|
||||||
├── build.yaml # Build settings
|
|
||||||
└── linuxkit/ # LinuxKit templates
|
|
||||||
```
|
|
||||||
|
|
||||||
And `repos.yaml` in workspace root for multi-repo management.
|
|
||||||
|
|
||||||
## Build Variants
|
|
||||||
|
|
||||||
Core supports build tags for different deployment contexts:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Full development binary (default)
|
|
||||||
go build -o core ./cmd/core/
|
|
||||||
|
|
||||||
# CI-only binary (minimal attack surface)
|
|
||||||
go build -tags ci -o core-ci ./cmd/core/
|
|
||||||
```
|
|
||||||
|
|
||||||
| Variant | Commands | Use Case |
|
|
||||||
|---------|----------|----------|
|
|
||||||
| `core` (default) | All commands | Development, local workflow |
|
|
||||||
| `core-ci` | build, ci, sdk, doctor | CI pipelines, production builds |
|
|
||||||
|
|
||||||
The CI variant excludes development tools (go, php, dev, pkg, vm, etc.) for a smaller attack surface in automated environments.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Go install (full binary)
|
|
||||||
CGO_ENABLED=0 go install github.com/host-uk/core/cmd/core@latest
|
|
||||||
|
|
||||||
# Or from source
|
|
||||||
cd /path/to/core
|
|
||||||
CGO_ENABLED=0 go install ./cmd/core/
|
|
||||||
|
|
||||||
# CI variant
|
|
||||||
CGO_ENABLED=0 go build -tags ci -o /usr/local/bin/core-ci ./cmd/core/
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify: `core doctor`
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
# Install the core skill globally for Claude Code
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# curl -fsSL https://raw.githubusercontent.com/host-uk/core/main/.claude/skills/core/install.sh | bash
|
|
||||||
#
|
|
||||||
# Or if you have the repo cloned:
|
|
||||||
# ./.claude/skills/core/install.sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SKILL_DIR="$HOME/.claude/skills/core"
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
|
||||||
# Check if running from repo or downloading
|
|
||||||
if [ -f "$SCRIPT_DIR/SKILL.md" ]; then
|
|
||||||
SOURCE_DIR="$SCRIPT_DIR"
|
|
||||||
else
|
|
||||||
# Download from GitHub
|
|
||||||
TEMP_DIR=$(mktemp -d)
|
|
||||||
trap "rm -rf $TEMP_DIR" EXIT
|
|
||||||
|
|
||||||
echo "Downloading core skill..."
|
|
||||||
curl -fsSL "https://raw.githubusercontent.com/host-uk/core/main/.claude/skills/core/SKILL.md" -o "$TEMP_DIR/SKILL.md"
|
|
||||||
SOURCE_DIR="$TEMP_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create skills directory if needed
|
|
||||||
mkdir -p "$SKILL_DIR"
|
|
||||||
|
|
||||||
# Copy skill file
|
|
||||||
cp "$SOURCE_DIR/SKILL.md" "$SKILL_DIR/SKILL.md"
|
|
||||||
|
|
||||||
echo "Installed core skill to $SKILL_DIR"
|
|
||||||
echo ""
|
|
||||||
echo "Usage:"
|
|
||||||
echo " - Claude will auto-invoke when working in host-uk repos"
|
|
||||||
echo " - Or type /core to invoke manually"
|
|
||||||
echo ""
|
|
||||||
echo "Commands available: core test, core build, core ci, core work, etc."
|
|
||||||
|
|
@ -98,12 +98,5 @@
|
||||||
"description": "Warn about uncommitted work after git commit"
|
"description": "Warn about uncommitted work after git commit"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"mcp": {
|
|
||||||
"core": {
|
|
||||||
"command": "core",
|
|
||||||
"args": ["mcp", "serve"],
|
|
||||||
"description": "Core CLI MCP server for multi-repo operations"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ package ai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared package
|
// Style aliases from shared package
|
||||||
|
|
@ -35,7 +34,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddAgenticCommands adds the agentic task management commands to the ai command.
|
// AddAgenticCommands adds the agentic task management commands to the ai command.
|
||||||
func AddAgenticCommands(parent *cobra.Command) {
|
func AddAgenticCommands(parent *cli.Command) {
|
||||||
// Task listing and viewing
|
// Task listing and viewing
|
||||||
addTasksCommand(parent)
|
addTasksCommand(parent)
|
||||||
addTaskCommand(parent)
|
addTaskCommand(parent)
|
||||||
|
|
|
||||||
|
|
@ -13,37 +13,36 @@ package ai
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cli.RegisterCommands(AddAICommands)
|
cli.RegisterCommands(AddAICommands)
|
||||||
}
|
}
|
||||||
|
|
||||||
var aiCmd = &cobra.Command{
|
var aiCmd = &cli.Command{
|
||||||
Use: "ai",
|
Use: "ai",
|
||||||
Short: i18n.T("cmd.ai.short"),
|
Short: i18n.T("cmd.ai.short"),
|
||||||
Long: i18n.T("cmd.ai.long"),
|
Long: i18n.T("cmd.ai.long"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var claudeCmd = &cobra.Command{
|
var claudeCmd = &cli.Command{
|
||||||
Use: "claude",
|
Use: "claude",
|
||||||
Short: i18n.T("cmd.ai.claude.short"),
|
Short: i18n.T("cmd.ai.claude.short"),
|
||||||
Long: i18n.T("cmd.ai.claude.long"),
|
Long: i18n.T("cmd.ai.claude.long"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var claudeRunCmd = &cobra.Command{
|
var claudeRunCmd = &cli.Command{
|
||||||
Use: "run",
|
Use: "run",
|
||||||
Short: i18n.T("cmd.ai.claude.run.short"),
|
Short: i18n.T("cmd.ai.claude.run.short"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runClaudeCode()
|
return runClaudeCode()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var claudeConfigCmd = &cobra.Command{
|
var claudeConfigCmd = &cli.Command{
|
||||||
Use: "config",
|
Use: "config",
|
||||||
Short: i18n.T("cmd.ai.claude.config.short"),
|
Short: i18n.T("cmd.ai.claude.config.short"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return showClaudeConfig()
|
return showClaudeConfig()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +60,7 @@ func initCommands() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAICommands registers the 'ai' command and all subcommands.
|
// AddAICommands registers the 'ai' command and all subcommands.
|
||||||
func AddAICommands(root *cobra.Command) {
|
func AddAICommands(root *cli.Command) {
|
||||||
initCommands()
|
initCommands()
|
||||||
root.AddCommand(aiCmd)
|
root.AddCommand(aiCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,14 @@ package ai
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/agentic"
|
"github.com/host-uk/core/pkg/agentic"
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// task:commit command flags
|
// task:commit command flags
|
||||||
|
|
@ -31,21 +30,21 @@ var (
|
||||||
taskPRBase string
|
taskPRBase string
|
||||||
)
|
)
|
||||||
|
|
||||||
var taskCommitCmd = &cobra.Command{
|
var taskCommitCmd = &cli.Command{
|
||||||
Use: "task:commit [task-id]",
|
Use: "task:commit [task-id]",
|
||||||
Short: i18n.T("cmd.ai.task_commit.short"),
|
Short: i18n.T("cmd.ai.task_commit.short"),
|
||||||
Long: i18n.T("cmd.ai.task_commit.long"),
|
Long: i18n.T("cmd.ai.task_commit.long"),
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
taskID := args[0]
|
taskID := args[0]
|
||||||
|
|
||||||
if taskCommitMessage == "" {
|
if taskCommitMessage == "" {
|
||||||
return fmt.Errorf("commit message required")
|
return cli.Err("commit message required")
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := agentic.NewClientFromConfig(cfg)
|
client := agentic.NewClientFromConfig(cfg)
|
||||||
|
|
@ -56,67 +55,67 @@ var taskCommitCmd = &cobra.Command{
|
||||||
// Get task details
|
// Get task details
|
||||||
task, err := client.GetTask(ctx, taskID)
|
task, err := client.GetTask(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "task"), err)
|
return cli.WrapVerb(err, "get", "task")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build commit message with optional scope
|
// Build commit message with optional scope
|
||||||
commitType := inferCommitType(task.Labels)
|
commitType := inferCommitType(task.Labels)
|
||||||
var fullMessage string
|
var fullMessage string
|
||||||
if taskCommitScope != "" {
|
if taskCommitScope != "" {
|
||||||
fullMessage = fmt.Sprintf("%s(%s): %s", commitType, taskCommitScope, taskCommitMessage)
|
fullMessage = cli.Sprintf("%s(%s): %s", commitType, taskCommitScope, taskCommitMessage)
|
||||||
} else {
|
} else {
|
||||||
fullMessage = fmt.Sprintf("%s: %s", commitType, taskCommitMessage)
|
fullMessage = cli.Sprintf("%s: %s", commitType, taskCommitMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current directory
|
// Get current directory
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for uncommitted changes
|
// Check for uncommitted changes
|
||||||
hasChanges, err := agentic.HasUncommittedChanges(ctx, cwd)
|
hasChanges, err := agentic.HasUncommittedChanges(ctx, cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.check", "git status"), err)
|
return cli.WrapVerb(err, "check", "git status")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasChanges {
|
if !hasChanges {
|
||||||
fmt.Println("No changes to commit")
|
cli.Text("No changes to commit")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create commit
|
// Create commit
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.ProgressSubject("create", "commit for "+taskID))
|
cli.Print("%s %s\n", dimStyle.Render(">>"), i18n.ProgressSubject("create", "commit for "+taskID))
|
||||||
if err := agentic.AutoCommit(ctx, task, cwd, fullMessage); err != nil {
|
if err := agentic.AutoCommit(ctx, task, cwd, fullMessage); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.commit"), err)
|
return cli.WrapAction(err, "commit")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.commit")+":", fullMessage)
|
cli.Print("%s %s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.commit")+":", fullMessage)
|
||||||
|
|
||||||
// Push if requested
|
// Push if requested
|
||||||
if taskCommitPush {
|
if taskCommitPush {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.Progress("push"))
|
cli.Print("%s %s\n", dimStyle.Render(">>"), i18n.Progress("push"))
|
||||||
if err := agentic.PushChanges(ctx, cwd); err != nil {
|
if err := agentic.PushChanges(ctx, cwd); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.push"), err)
|
return cli.WrapAction(err, "push")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.push", "changes"))
|
cli.Print("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.push", "changes"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var taskPRCmd = &cobra.Command{
|
var taskPRCmd = &cli.Command{
|
||||||
Use: "task:pr [task-id]",
|
Use: "task:pr [task-id]",
|
||||||
Short: i18n.T("cmd.ai.task_pr.short"),
|
Short: i18n.T("cmd.ai.task_pr.short"),
|
||||||
Long: i18n.T("cmd.ai.task_pr.long"),
|
Long: i18n.T("cmd.ai.task_pr.long"),
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
taskID := args[0]
|
taskID := args[0]
|
||||||
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := agentic.NewClientFromConfig(cfg)
|
client := agentic.NewClientFromConfig(cfg)
|
||||||
|
|
@ -127,31 +126,31 @@ var taskPRCmd = &cobra.Command{
|
||||||
// Get task details
|
// Get task details
|
||||||
task, err := client.GetTask(ctx, taskID)
|
task, err := client.GetTask(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "task"), err)
|
return cli.WrapVerb(err, "get", "task")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current directory
|
// Get current directory
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check current branch
|
// Check current branch
|
||||||
branch, err := agentic.GetCurrentBranch(ctx, cwd)
|
branch, err := agentic.GetCurrentBranch(ctx, cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "branch"), err)
|
return cli.WrapVerb(err, "get", "branch")
|
||||||
}
|
}
|
||||||
|
|
||||||
if branch == "main" || branch == "master" {
|
if branch == "main" || branch == "master" {
|
||||||
return fmt.Errorf("cannot create PR from %s branch", branch)
|
return cli.Err("cannot create PR from %s branch", branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push current branch
|
// Push current branch
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.ProgressSubject("push", branch))
|
cli.Print("%s %s\n", dimStyle.Render(">>"), i18n.ProgressSubject("push", branch))
|
||||||
if err := agentic.PushChanges(ctx, cwd); err != nil {
|
if err := agentic.PushChanges(ctx, cwd); err != nil {
|
||||||
// Try setting upstream
|
// Try setting upstream
|
||||||
if _, err := runGitCommand(cwd, "push", "-u", "origin", branch); err != nil {
|
if _, err := runGitCommand(cwd, "push", "-u", "origin", branch); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.push", "branch"), err)
|
return cli.WrapVerb(err, "push", "branch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,14 +166,14 @@ var taskPRCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create PR
|
// Create PR
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.ProgressSubject("create", "PR"))
|
cli.Print("%s %s\n", dimStyle.Render(">>"), i18n.ProgressSubject("create", "PR"))
|
||||||
prURL, err := agentic.CreatePR(ctx, task, cwd, opts)
|
prURL, err := agentic.CreatePR(ctx, task, cwd, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.create", "PR"), err)
|
return cli.WrapVerb(err, "create", "PR")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.create", "PR"))
|
cli.Print("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.create", "PR"))
|
||||||
fmt.Printf(" %s %s\n", i18n.Label("url"), prURL)
|
cli.Print(" %s %s\n", i18n.Label("url"), prURL)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -193,12 +192,12 @@ func initGitFlags() {
|
||||||
taskPRCmd.Flags().StringVar(&taskPRBase, "base", "", i18n.T("cmd.ai.task_pr.flag.base"))
|
taskPRCmd.Flags().StringVar(&taskPRBase, "base", "", i18n.T("cmd.ai.task_pr.flag.base"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskCommitCommand(parent *cobra.Command) {
|
func addTaskCommitCommand(parent *cli.Command) {
|
||||||
initGitFlags()
|
initGitFlags()
|
||||||
parent.AddCommand(taskCommitCmd)
|
parent.AddCommand(taskCommitCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskPRCommand(parent *cobra.Command) {
|
func addTaskPRCommand(parent *cli.Command) {
|
||||||
parent.AddCommand(taskPRCmd)
|
parent.AddCommand(taskPRCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,7 +239,7 @@ func runGitCommand(dir string, args ...string) (string, error) {
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
if stderr.Len() > 0 {
|
if stderr.Len() > 0 {
|
||||||
return "", fmt.Errorf("%w: %s", err, stderr.String())
|
return "", cli.Wrap(err, stderr.String())
|
||||||
}
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,14 @@ package ai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/agentic"
|
"github.com/host-uk/core/pkg/agentic"
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// tasks command flags
|
// tasks command flags
|
||||||
|
|
@ -31,11 +30,11 @@ var (
|
||||||
taskShowContext bool
|
taskShowContext bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var tasksCmd = &cobra.Command{
|
var tasksCmd = &cli.Command{
|
||||||
Use: "tasks",
|
Use: "tasks",
|
||||||
Short: i18n.T("cmd.ai.tasks.short"),
|
Short: i18n.T("cmd.ai.tasks.short"),
|
||||||
Long: i18n.T("cmd.ai.tasks.long"),
|
Long: i18n.T("cmd.ai.tasks.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
limit := tasksLimit
|
limit := tasksLimit
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
limit = 20
|
limit = 20
|
||||||
|
|
@ -43,7 +42,7 @@ var tasksCmd = &cobra.Command{
|
||||||
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := agentic.NewClientFromConfig(cfg)
|
client := agentic.NewClientFromConfig(cfg)
|
||||||
|
|
@ -68,11 +67,11 @@ var tasksCmd = &cobra.Command{
|
||||||
|
|
||||||
tasks, err := client.ListTasks(ctx, opts)
|
tasks, err := client.ListTasks(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.list", "tasks"), err)
|
return cli.WrapVerb(err, "list", "tasks")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tasks) == 0 {
|
if len(tasks) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.ai.tasks.none_found"))
|
cli.Text(i18n.T("cmd.ai.tasks.none_found"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,14 +80,14 @@ var tasksCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var taskCmd = &cobra.Command{
|
var taskCmd = &cli.Command{
|
||||||
Use: "task [task-id]",
|
Use: "task [task-id]",
|
||||||
Short: i18n.T("cmd.ai.task.short"),
|
Short: i18n.T("cmd.ai.task.short"),
|
||||||
Long: i18n.T("cmd.ai.task.long"),
|
Long: i18n.T("cmd.ai.task.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := agentic.NewClientFromConfig(cfg)
|
client := agentic.NewClientFromConfig(cfg)
|
||||||
|
|
@ -111,11 +110,11 @@ var taskCmd = &cobra.Command{
|
||||||
Limit: 50,
|
Limit: 50,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.list", "tasks"), err)
|
return cli.WrapVerb(err, "list", "tasks")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tasks) == 0 {
|
if len(tasks) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.ai.task.no_pending"))
|
cli.Text(i18n.T("cmd.ai.task.no_pending"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,12 +134,12 @@ var taskCmd = &cobra.Command{
|
||||||
taskClaim = true // Auto-select implies claiming
|
taskClaim = true // Auto-select implies claiming
|
||||||
} else {
|
} else {
|
||||||
if taskID == "" {
|
if taskID == "" {
|
||||||
return fmt.Errorf("%s", i18n.T("cmd.ai.task.id_required"))
|
return cli.Err(i18n.T("cmd.ai.task.id_required"))
|
||||||
}
|
}
|
||||||
|
|
||||||
task, err = client.GetTask(ctx, taskID)
|
task, err = client.GetTask(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "task"), err)
|
return cli.WrapVerb(err, "get", "task")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,25 +148,25 @@ var taskCmd = &cobra.Command{
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
taskCtx, err := agentic.BuildTaskContext(task, cwd)
|
taskCtx, err := agentic.BuildTaskContext(task, cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s %s: %s\n", errorStyle.Render(">>"), i18n.T("i18n.fail.build", "context"), err)
|
cli.Print("%s %s: %s\n", errorStyle.Render(">>"), i18n.T("i18n.fail.build", "context"), err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(taskCtx.FormatContext())
|
cli.Text(taskCtx.FormatContext())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printTaskDetails(task)
|
printTaskDetails(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
if taskClaim && task.Status == agentic.StatusPending {
|
if taskClaim && task.Status == agentic.StatusPending {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.ai.task.claiming"))
|
cli.Print("%s %s\n", dimStyle.Render(">>"), i18n.T("cmd.ai.task.claiming"))
|
||||||
|
|
||||||
claimedTask, err := client.ClaimTask(ctx, task.ID)
|
claimedTask, err := client.ClaimTask(ctx, task.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.claim", "task"), err)
|
return cli.WrapVerb(err, "claim", "task")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.claim", "task"))
|
cli.Print("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.claim", "task"))
|
||||||
fmt.Printf(" %s %s\n", i18n.Label("status"), formatTaskStatus(claimedTask.Status))
|
cli.Print(" %s %s\n", i18n.Label("status"), formatTaskStatus(claimedTask.Status))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -188,17 +187,17 @@ func initTasksFlags() {
|
||||||
taskCmd.Flags().BoolVar(&taskShowContext, "context", false, i18n.T("cmd.ai.task.flag.context"))
|
taskCmd.Flags().BoolVar(&taskShowContext, "context", false, i18n.T("cmd.ai.task.flag.context"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTasksCommand(parent *cobra.Command) {
|
func addTasksCommand(parent *cli.Command) {
|
||||||
initTasksFlags()
|
initTasksFlags()
|
||||||
parent.AddCommand(tasksCmd)
|
parent.AddCommand(tasksCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskCommand(parent *cobra.Command) {
|
func addTaskCommand(parent *cli.Command) {
|
||||||
parent.AddCommand(taskCmd)
|
parent.AddCommand(taskCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTaskList(tasks []agentic.Task) {
|
func printTaskList(tasks []agentic.Task) {
|
||||||
fmt.Printf("\n%s\n\n", i18n.T("cmd.ai.tasks.found", map[string]interface{}{"Count": len(tasks)}))
|
cli.Print("\n%s\n\n", i18n.T("cmd.ai.tasks.found", map[string]interface{}{"Count": len(tasks)}))
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
id := taskIDStyle.Render(task.ID)
|
id := taskIDStyle.Render(task.ID)
|
||||||
|
|
@ -206,56 +205,56 @@ func printTaskList(tasks []agentic.Task) {
|
||||||
priority := formatTaskPriority(task.Priority)
|
priority := formatTaskPriority(task.Priority)
|
||||||
status := formatTaskStatus(task.Status)
|
status := formatTaskStatus(task.Status)
|
||||||
|
|
||||||
line := fmt.Sprintf(" %s %s %s %s", id, priority, status, title)
|
line := cli.Sprintf(" %s %s %s %s", id, priority, status, title)
|
||||||
|
|
||||||
if len(task.Labels) > 0 {
|
if len(task.Labels) > 0 {
|
||||||
labels := taskLabelStyle.Render("[" + strings.Join(task.Labels, ", ") + "]")
|
labels := taskLabelStyle.Render("[" + strings.Join(task.Labels, ", ") + "]")
|
||||||
line += " " + labels
|
line += " " + labels
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(line)
|
cli.Text(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.ai.tasks.hint")))
|
cli.Print("%s\n", dimStyle.Render(i18n.T("cmd.ai.tasks.hint")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTaskDetails(task *agentic.Task) {
|
func printTaskDetails(task *agentic.Task) {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.id")), taskIDStyle.Render(task.ID))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.id")), taskIDStyle.Render(task.ID))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.title")), taskTitleStyle.Render(task.Title))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.title")), taskTitleStyle.Render(task.Title))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.priority")), formatTaskPriority(task.Priority))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.priority")), formatTaskPriority(task.Priority))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("status")), formatTaskStatus(task.Status))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("status")), formatTaskStatus(task.Status))
|
||||||
|
|
||||||
if task.Project != "" {
|
if task.Project != "" {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("project")), task.Project)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("project")), task.Project)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(task.Labels) > 0 {
|
if len(task.Labels) > 0 {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.labels")), taskLabelStyle.Render(strings.Join(task.Labels, ", ")))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.labels")), taskLabelStyle.Render(strings.Join(task.Labels, ", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.ClaimedBy != "" {
|
if task.ClaimedBy != "" {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.claimed_by")), task.ClaimedBy)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.claimed_by")), task.ClaimedBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.created")), formatAge(task.CreatedAt))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.created")), formatAge(task.CreatedAt))
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.ai.label.description")))
|
cli.Print("%s\n", dimStyle.Render(i18n.T("cmd.ai.label.description")))
|
||||||
fmt.Println(task.Description)
|
cli.Text(task.Description)
|
||||||
|
|
||||||
if len(task.Files) > 0 {
|
if len(task.Files) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.ai.label.related_files")))
|
cli.Print("%s\n", dimStyle.Render(i18n.T("cmd.ai.label.related_files")))
|
||||||
for _, f := range task.Files {
|
for _, f := range task.Files {
|
||||||
fmt.Printf(" - %s\n", f)
|
cli.Print(" - %s\n", f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(task.Dependencies) > 0 {
|
if len(task.Dependencies) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.blocked_by")), strings.Join(task.Dependencies, ", "))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.ai.label.blocked_by")), strings.Join(task.Dependencies, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,11 @@ package ai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/agentic"
|
"github.com/host-uk/core/pkg/agentic"
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// task:update command flags
|
// task:update command flags
|
||||||
|
|
@ -26,21 +25,21 @@ var (
|
||||||
taskCompleteErrorMsg string
|
taskCompleteErrorMsg string
|
||||||
)
|
)
|
||||||
|
|
||||||
var taskUpdateCmd = &cobra.Command{
|
var taskUpdateCmd = &cli.Command{
|
||||||
Use: "task:update [task-id]",
|
Use: "task:update [task-id]",
|
||||||
Short: i18n.T("cmd.ai.task_update.short"),
|
Short: i18n.T("cmd.ai.task_update.short"),
|
||||||
Long: i18n.T("cmd.ai.task_update.long"),
|
Long: i18n.T("cmd.ai.task_update.long"),
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
taskID := args[0]
|
taskID := args[0]
|
||||||
|
|
||||||
if taskUpdateStatus == "" && taskUpdateProgress == 0 && taskUpdateNotes == "" {
|
if taskUpdateStatus == "" && taskUpdateProgress == 0 && taskUpdateNotes == "" {
|
||||||
return fmt.Errorf("%s", i18n.T("cmd.ai.task_update.flag_required"))
|
return cli.Err(i18n.T("cmd.ai.task_update.flag_required"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := agentic.NewClientFromConfig(cfg)
|
client := agentic.NewClientFromConfig(cfg)
|
||||||
|
|
@ -57,25 +56,25 @@ var taskUpdateCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.UpdateTask(ctx, taskID, update); err != nil {
|
if err := client.UpdateTask(ctx, taskID, update); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.update", "task"), err)
|
return cli.WrapVerb(err, "update", "task")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.update", "task"))
|
cli.Print("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.update", "task"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var taskCompleteCmd = &cobra.Command{
|
var taskCompleteCmd = &cli.Command{
|
||||||
Use: "task:complete [task-id]",
|
Use: "task:complete [task-id]",
|
||||||
Short: i18n.T("cmd.ai.task_complete.short"),
|
Short: i18n.T("cmd.ai.task_complete.short"),
|
||||||
Long: i18n.T("cmd.ai.task_complete.long"),
|
Long: i18n.T("cmd.ai.task_complete.long"),
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
taskID := args[0]
|
taskID := args[0]
|
||||||
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := agentic.NewClientFromConfig(cfg)
|
client := agentic.NewClientFromConfig(cfg)
|
||||||
|
|
@ -90,13 +89,13 @@ var taskCompleteCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.CompleteTask(ctx, taskID, result); err != nil {
|
if err := client.CompleteTask(ctx, taskID, result); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.complete", "task"), err)
|
return cli.WrapVerb(err, "complete", "task")
|
||||||
}
|
}
|
||||||
|
|
||||||
if taskCompleteFailed {
|
if taskCompleteFailed {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(">>"), i18n.T("cmd.ai.task_complete.failed", map[string]interface{}{"ID": taskID}))
|
cli.Print("%s %s\n", errorStyle.Render(">>"), i18n.T("cmd.ai.task_complete.failed", map[string]interface{}{"ID": taskID}))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.complete", "task"))
|
cli.Print("%s %s\n", successStyle.Render(">>"), i18n.T("i18n.done.complete", "task"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -114,11 +113,11 @@ func initUpdatesFlags() {
|
||||||
taskCompleteCmd.Flags().StringVar(&taskCompleteErrorMsg, "error", "", i18n.T("cmd.ai.task_complete.flag.error"))
|
taskCompleteCmd.Flags().StringVar(&taskCompleteErrorMsg, "error", "", i18n.T("cmd.ai.task_complete.flag.error"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskUpdateCommand(parent *cobra.Command) {
|
func addTaskUpdateCommand(parent *cli.Command) {
|
||||||
initUpdatesFlags()
|
initUpdatesFlags()
|
||||||
parent.AddCommand(taskUpdateCmd)
|
parent.AddCommand(taskUpdateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskCompleteCommand(parent *cobra.Command) {
|
func addTaskCompleteCommand(parent *cli.Command) {
|
||||||
parent.AddCommand(taskCompleteCmd)
|
parent.AddCommand(taskCompleteCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
package ci
|
package ci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/release"
|
"github.com/host-uk/core/pkg/release"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,21 +11,21 @@ import (
|
||||||
func runChangelog(fromRef, toRef string) error {
|
func runChangelog(fromRef, toRef string) error {
|
||||||
projectDir, err := os.Getwd()
|
projectDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load config for changelog settings
|
// Load config for changelog settings
|
||||||
cfg, err := release.LoadConfig(projectDir)
|
cfg, err := release.LoadConfig(projectDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate changelog
|
// Generate changelog
|
||||||
changelog, err := release.GenerateWithConfig(projectDir, fromRef, toRef, &cfg.Changelog)
|
changelog, err := release.GenerateWithConfig(projectDir, fromRef, toRef, &cfg.Changelog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.generate", "changelog"), err)
|
return cli.WrapVerb(err, "generate", "changelog")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(changelog)
|
cli.Text(changelog)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ package ci
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared
|
// Style aliases from shared
|
||||||
|
|
@ -30,39 +29,39 @@ var (
|
||||||
changelogToRef string
|
changelogToRef string
|
||||||
)
|
)
|
||||||
|
|
||||||
var ciCmd = &cobra.Command{
|
var ciCmd = &cli.Command{
|
||||||
Use: "ci",
|
Use: "ci",
|
||||||
Short: i18n.T("cmd.ci.short"),
|
Short: i18n.T("cmd.ci.short"),
|
||||||
Long: i18n.T("cmd.ci.long"),
|
Long: i18n.T("cmd.ci.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
dryRun := !ciGoForLaunch
|
dryRun := !ciGoForLaunch
|
||||||
return runCIPublish(dryRun, ciVersion, ciDraft, ciPrerelease)
|
return runCIPublish(dryRun, ciVersion, ciDraft, ciPrerelease)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ciInitCmd = &cobra.Command{
|
var ciInitCmd = &cli.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
Short: i18n.T("cmd.ci.init.short"),
|
Short: i18n.T("cmd.ci.init.short"),
|
||||||
Long: i18n.T("cmd.ci.init.long"),
|
Long: i18n.T("cmd.ci.init.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runCIReleaseInit()
|
return runCIReleaseInit()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ciChangelogCmd = &cobra.Command{
|
var ciChangelogCmd = &cli.Command{
|
||||||
Use: "changelog",
|
Use: "changelog",
|
||||||
Short: i18n.T("cmd.ci.changelog.short"),
|
Short: i18n.T("cmd.ci.changelog.short"),
|
||||||
Long: i18n.T("cmd.ci.changelog.long"),
|
Long: i18n.T("cmd.ci.changelog.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runChangelog(changelogFromRef, changelogToRef)
|
return runChangelog(changelogFromRef, changelogToRef)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ciVersionCmd = &cobra.Command{
|
var ciVersionCmd = &cli.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: i18n.T("cmd.ci.version.short"),
|
Short: i18n.T("cmd.ci.version.short"),
|
||||||
Long: i18n.T("cmd.ci.version.long"),
|
Long: i18n.T("cmd.ci.version.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runCIReleaseVersion()
|
return runCIReleaseVersion()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ package ci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -19,6 +18,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCICommands registers the 'ci' command and all subcommands.
|
// AddCICommands registers the 'ci' command and all subcommands.
|
||||||
func AddCICommands(root *cobra.Command) {
|
func AddCICommands(root *cli.Command) {
|
||||||
root.AddCommand(ciCmd)
|
root.AddCommand(ciCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ package ci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/release"
|
"github.com/host-uk/core/pkg/release"
|
||||||
)
|
)
|
||||||
|
|
@ -15,34 +15,34 @@ import (
|
||||||
func runCIReleaseInit() error {
|
func runCIReleaseInit() error {
|
||||||
projectDir, err := os.Getwd()
|
projectDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if config already exists
|
// Check if config already exists
|
||||||
if release.ConfigExists(projectDir) {
|
if release.ConfigExists(projectDir) {
|
||||||
fmt.Printf("%s %s %s\n",
|
cli.Print("%s %s %s\n",
|
||||||
releaseDimStyle.Render(i18n.Label("note")),
|
releaseDimStyle.Render(i18n.Label("note")),
|
||||||
i18n.T("cmd.ci.init.config_exists"),
|
i18n.T("cmd.ci.init.config_exists"),
|
||||||
release.ConfigPath(projectDir))
|
release.ConfigPath(projectDir))
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
fmt.Print(i18n.T("cmd.ci.init.overwrite_prompt"))
|
cli.Print("%s", i18n.T("cmd.ci.init.overwrite_prompt"))
|
||||||
response, _ := reader.ReadString('\n')
|
response, _ := reader.ReadString('\n')
|
||||||
response = strings.TrimSpace(strings.ToLower(response))
|
response = strings.TrimSpace(strings.ToLower(response))
|
||||||
if response != "y" && response != "yes" {
|
if response != "y" && response != "yes" {
|
||||||
fmt.Println(i18n.T("common.prompt.abort"))
|
cli.Text(i18n.T("common.prompt.abort"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", releaseHeaderStyle.Render(i18n.T("cmd.ci.label.init")), i18n.T("cmd.ci.init.creating"))
|
cli.Print("%s %s\n", releaseHeaderStyle.Render(i18n.T("cmd.ci.label.init")), i18n.T("cmd.ci.init.creating"))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
// Project name
|
// Project name
|
||||||
defaultName := filepath.Base(projectDir)
|
defaultName := filepath.Base(projectDir)
|
||||||
fmt.Printf("%s [%s]: ", i18n.T("cmd.ci.init.project_name"), defaultName)
|
cli.Print("%s [%s]: ", i18n.T("cmd.ci.init.project_name"), defaultName)
|
||||||
name, _ := reader.ReadString('\n')
|
name, _ := reader.ReadString('\n')
|
||||||
name = strings.TrimSpace(name)
|
name = strings.TrimSpace(name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
|
@ -50,7 +50,7 @@ func runCIReleaseInit() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repository
|
// Repository
|
||||||
fmt.Printf("%s ", i18n.T("cmd.ci.init.github_repo"))
|
cli.Print("%s ", i18n.T("cmd.ci.init.github_repo"))
|
||||||
repo, _ := reader.ReadString('\n')
|
repo, _ := reader.ReadString('\n')
|
||||||
repo = strings.TrimSpace(repo)
|
repo = strings.TrimSpace(repo)
|
||||||
|
|
||||||
|
|
@ -61,11 +61,11 @@ func runCIReleaseInit() error {
|
||||||
|
|
||||||
// Write config
|
// Write config
|
||||||
if err := release.WriteConfig(cfg, projectDir); err != nil {
|
if err := release.WriteConfig(cfg, projectDir); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.write", "config"), err)
|
return cli.WrapVerb(err, "write", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s %s %s\n",
|
cli.Print("%s %s %s\n",
|
||||||
releaseSuccessStyle.Render(i18n.T("i18n.done.pass")),
|
releaseSuccessStyle.Render(i18n.T("i18n.done.pass")),
|
||||||
i18n.T("cmd.ci.init.config_written"),
|
i18n.T("cmd.ci.init.config_written"),
|
||||||
release.ConfigPath(projectDir))
|
release.ConfigPath(projectDir))
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ package ci
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/release"
|
"github.com/host-uk/core/pkg/release"
|
||||||
)
|
)
|
||||||
|
|
@ -18,13 +18,13 @@ func runCIPublish(dryRun bool, version string, draft, prerelease bool) error {
|
||||||
// Get current directory
|
// Get current directory
|
||||||
projectDir, err := os.Getwd()
|
projectDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
cfg, err := release.LoadConfig(projectDir)
|
cfg, err := release.LoadConfig(projectDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "config"), err)
|
return cli.WrapVerb(err, "load", "config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply CLI overrides
|
// Apply CLI overrides
|
||||||
|
|
@ -45,13 +45,13 @@ func runCIPublish(dryRun bool, version string, draft, prerelease bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print header
|
// Print header
|
||||||
fmt.Printf("%s %s\n", releaseHeaderStyle.Render(i18n.T("cmd.ci.label.ci")), i18n.T("cmd.ci.publishing"))
|
cli.Print("%s %s\n", releaseHeaderStyle.Render(i18n.T("cmd.ci.label.ci")), i18n.T("cmd.ci.publishing"))
|
||||||
if dryRun {
|
if dryRun {
|
||||||
fmt.Printf(" %s\n", releaseDimStyle.Render(i18n.T("cmd.ci.dry_run_hint")))
|
cli.Print(" %s\n", releaseDimStyle.Render(i18n.T("cmd.ci.dry_run_hint")))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s\n", releaseSuccessStyle.Render(i18n.T("cmd.ci.go_for_launch")))
|
cli.Print(" %s\n", releaseSuccessStyle.Render(i18n.T("cmd.ci.go_for_launch")))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Check for publishers
|
// Check for publishers
|
||||||
if len(cfg.Publishers) == 0 {
|
if len(cfg.Publishers) == 0 {
|
||||||
|
|
@ -61,19 +61,19 @@ func runCIPublish(dryRun bool, version string, draft, prerelease bool) error {
|
||||||
// Publish pre-built artifacts
|
// Publish pre-built artifacts
|
||||||
rel, err := release.Publish(ctx, cfg, dryRun)
|
rel, err := release.Publish(ctx, cfg, dryRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s %v\n", releaseErrorStyle.Render(i18n.Label("error")), err)
|
cli.Print("%s %v\n", releaseErrorStyle.Render(i18n.Label("error")), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print summary
|
// Print summary
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s %s\n", releaseSuccessStyle.Render(i18n.T("i18n.done.pass")), i18n.T("cmd.ci.publish_completed"))
|
cli.Print("%s %s\n", releaseSuccessStyle.Render(i18n.T("i18n.done.pass")), i18n.T("cmd.ci.publish_completed"))
|
||||||
fmt.Printf(" %s %s\n", i18n.Label("version"), releaseValueStyle.Render(rel.Version))
|
cli.Print(" %s %s\n", i18n.Label("version"), releaseValueStyle.Render(rel.Version))
|
||||||
fmt.Printf(" %s %d\n", i18n.T("cmd.ci.label.artifacts"), len(rel.Artifacts))
|
cli.Print(" %s %d\n", i18n.T("cmd.ci.label.artifacts"), len(rel.Artifacts))
|
||||||
|
|
||||||
if !dryRun {
|
if !dryRun {
|
||||||
for _, pub := range cfg.Publishers {
|
for _, pub := range cfg.Publishers {
|
||||||
fmt.Printf(" %s %s\n", i18n.T("cmd.ci.label.published"), releaseValueStyle.Render(pub.Type))
|
cli.Print(" %s %s\n", i18n.T("cmd.ci.label.published"), releaseValueStyle.Render(pub.Type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package ci
|
package ci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/release"
|
"github.com/host-uk/core/pkg/release"
|
||||||
)
|
)
|
||||||
|
|
@ -12,14 +12,14 @@ import (
|
||||||
func runCIReleaseVersion() error {
|
func runCIReleaseVersion() error {
|
||||||
projectDir, err := os.Getwd()
|
projectDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
version, err := release.DetermineVersion(projectDir)
|
version, err := release.DetermineVersion(projectDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.determine", "version"), err)
|
return cli.WrapVerb(err, "determine", "version")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", i18n.Label("version"), releaseValueStyle.Render(version))
|
cli.Print("%s %s\n", i18n.Label("version"), releaseValueStyle.Render(version))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -489,8 +489,8 @@ func Path(p string) string {
|
||||||
return CodeStyle.Render(p)
|
return CodeStyle.Render(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command renders a command in code style.
|
// CommandStr renders a command string in code style.
|
||||||
func Command(cmd string) string {
|
func CommandStr(cmd string) string {
|
||||||
return CodeStyle.Render(cmd)
|
return CodeStyle.Render(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
package dev
|
package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// addAPICommands adds the 'api' command and its subcommands to the given parent command.
|
// addAPICommands adds the 'api' command and its subcommands to the given parent command.
|
||||||
func addAPICommands(parent *cobra.Command) {
|
func addAPICommands(parent *cli.Command) {
|
||||||
// Create the 'api' command
|
// Create the 'api' command
|
||||||
apiCmd := &cobra.Command{
|
apiCmd := &cli.Command{
|
||||||
Use: "api",
|
Use: "api",
|
||||||
Short: i18n.T("cmd.dev.api.short"),
|
Short: i18n.T("cmd.dev.api.short"),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package dev
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -12,7 +11,6 @@ import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CI-specific styles (aliases to shared)
|
// CI-specific styles (aliases to shared)
|
||||||
|
|
@ -45,12 +43,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addCICommand adds the 'ci' command to the given parent command.
|
// addCICommand adds the 'ci' command to the given parent command.
|
||||||
func addCICommand(parent *cobra.Command) {
|
func addCICommand(parent *cli.Command) {
|
||||||
ciCmd := &cobra.Command{
|
ciCmd := &cli.Command{
|
||||||
Use: "ci",
|
Use: "ci",
|
||||||
Short: i18n.T("cmd.dev.ci.short"),
|
Short: i18n.T("cmd.dev.ci.short"),
|
||||||
Long: i18n.T("cmd.dev.ci.long"),
|
Long: i18n.T("cmd.dev.ci.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
branch := ciBranch
|
branch := ciBranch
|
||||||
if branch == "" {
|
if branch == "" {
|
||||||
branch = "main"
|
branch = "main"
|
||||||
|
|
@ -79,20 +77,20 @@ func runCI(registryPath string, branch string, failedOnly bool) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to scan directory: %w", err)
|
return cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,15 +102,15 @@ func runCI(registryPath string, branch string, failedOnly bool) error {
|
||||||
|
|
||||||
repoList := reg.List()
|
repoList := reg.List()
|
||||||
for i, repo := range repoList {
|
for i, repo := range repoList {
|
||||||
repoFullName := fmt.Sprintf("%s/%s", reg.Org, repo.Name)
|
repoFullName := cli.Sprintf("%s/%s", reg.Org, repo.Name)
|
||||||
fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("i18n.progress.check")), i+1, len(repoList), repo.Name)
|
cli.Print("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("i18n.progress.check")), i+1, len(repoList), repo.Name)
|
||||||
|
|
||||||
runs, err := fetchWorkflowRuns(repoFullName, repo.Name, branch)
|
runs, err := fetchWorkflowRuns(repoFullName, repo.Name, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no workflows") {
|
if strings.Contains(err.Error(), "no workflows") {
|
||||||
noCI = append(noCI, repo.Name)
|
noCI = append(noCI, repo.Name)
|
||||||
} else {
|
} else {
|
||||||
fetchErrors = append(fetchErrors, fmt.Errorf("%s: %w", repo.Name, err))
|
fetchErrors = append(fetchErrors, cli.Wrap(err, repo.Name))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +122,7 @@ func runCI(registryPath string, branch string, failedOnly bool) error {
|
||||||
noCI = append(noCI, repo.Name)
|
noCI = append(noCI, repo.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Print("\033[2K\r") // Clear progress line
|
cli.Print("\033[2K\r") // Clear progress line
|
||||||
|
|
||||||
// Count by status
|
// Count by status
|
||||||
var success, failed, pending, other int
|
var success, failed, pending, other int
|
||||||
|
|
@ -146,22 +144,22 @@ func runCI(registryPath string, branch string, failedOnly bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print summary
|
// Print summary
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s", i18n.T("cmd.dev.ci.repos_checked", map[string]interface{}{"Count": len(repoList)}))
|
cli.Print("%s", i18n.T("cmd.dev.ci.repos_checked", map[string]interface{}{"Count": len(repoList)}))
|
||||||
if success > 0 {
|
if success > 0 {
|
||||||
fmt.Printf(" * %s", ciSuccessStyle.Render(i18n.T("cmd.dev.ci.passing", map[string]interface{}{"Count": success})))
|
cli.Print(" * %s", ciSuccessStyle.Render(i18n.T("cmd.dev.ci.passing", map[string]interface{}{"Count": success})))
|
||||||
}
|
}
|
||||||
if failed > 0 {
|
if failed > 0 {
|
||||||
fmt.Printf(" * %s", ciFailureStyle.Render(i18n.T("cmd.dev.ci.failing", map[string]interface{}{"Count": failed})))
|
cli.Print(" * %s", ciFailureStyle.Render(i18n.T("cmd.dev.ci.failing", map[string]interface{}{"Count": failed})))
|
||||||
}
|
}
|
||||||
if pending > 0 {
|
if pending > 0 {
|
||||||
fmt.Printf(" * %s", ciPendingStyle.Render(i18n.T("common.count.pending", map[string]interface{}{"Count": pending})))
|
cli.Print(" * %s", ciPendingStyle.Render(i18n.T("common.count.pending", map[string]interface{}{"Count": pending})))
|
||||||
}
|
}
|
||||||
if len(noCI) > 0 {
|
if len(noCI) > 0 {
|
||||||
fmt.Printf(" * %s", ciSkippedStyle.Render(i18n.T("cmd.dev.ci.no_ci", map[string]interface{}{"Count": len(noCI)})))
|
cli.Print(" * %s", ciSkippedStyle.Render(i18n.T("cmd.dev.ci.no_ci", map[string]interface{}{"Count": len(noCI)})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Filter if needed
|
// Filter if needed
|
||||||
displayRuns := allRuns
|
displayRuns := allRuns
|
||||||
|
|
@ -181,9 +179,9 @@ func runCI(registryPath string, branch string, failedOnly bool) error {
|
||||||
|
|
||||||
// Print errors
|
// Print errors
|
||||||
if len(fetchErrors) > 0 {
|
if len(fetchErrors) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
for _, err := range fetchErrors {
|
for _, err := range fetchErrors {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.Label("error")), err)
|
cli.Print("%s %s\n", errorStyle.Render(i18n.Label("error")), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +202,7 @@ func fetchWorkflowRuns(repoFullName, repoName string, branch string) ([]Workflow
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
stderr := string(exitErr.Stderr)
|
stderr := string(exitErr.Stderr)
|
||||||
return nil, fmt.Errorf("%s", strings.TrimSpace(stderr))
|
return nil, cli.Err("%s", strings.TrimSpace(stderr))
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +250,7 @@ func printWorkflowRun(run WorkflowRun) {
|
||||||
// Age
|
// Age
|
||||||
age := cli.FormatAge(run.UpdatedAt)
|
age := cli.FormatAge(run.UpdatedAt)
|
||||||
|
|
||||||
fmt.Printf(" %s %-18s %-22s %s\n",
|
cli.Print(" %s %-18s %-22s %s\n",
|
||||||
status,
|
status,
|
||||||
repoNameStyle.Render(run.RepoName),
|
repoNameStyle.Render(run.RepoName),
|
||||||
dimStyle.Render(workflowName),
|
dimStyle.Render(workflowName),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
|
@ -10,7 +9,6 @@ import (
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commit command flags
|
// Commit command flags
|
||||||
|
|
@ -20,12 +18,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addCommitCommand adds the 'commit' command to the given parent command.
|
// addCommitCommand adds the 'commit' command to the given parent command.
|
||||||
func addCommitCommand(parent *cobra.Command) {
|
func addCommitCommand(parent *cli.Command) {
|
||||||
commitCmd := &cobra.Command{
|
commitCmd := &cli.Command{
|
||||||
Use: "commit",
|
Use: "commit",
|
||||||
Short: i18n.T("cmd.dev.commit.short"),
|
Short: i18n.T("cmd.dev.commit.short"),
|
||||||
Long: i18n.T("cmd.dev.commit.long"),
|
Long: i18n.T("cmd.dev.commit.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runCommit(commitRegistryPath, commitAll)
|
return runCommit(commitRegistryPath, commitAll)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -52,24 +50,24 @@ func runCommit(registryPath string, all bool) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory for repos
|
// Fallback: scan current directory for repos
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to scan directory: %w", err)
|
return cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
||||||
registryPath = cwd
|
registryPath = cwd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +84,7 @@ func runCommit(registryPath string, all bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.no_git_repos"))
|
cli.Text(i18n.T("cmd.dev.no_git_repos"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,58 +103,58 @@ func runCommit(registryPath string, all bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(dirtyRepos) == 0 {
|
if len(dirtyRepos) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.no_changes"))
|
cli.Text(i18n.T("cmd.dev.no_changes"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show dirty repos
|
// Show dirty repos
|
||||||
fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.repos_with_changes", map[string]interface{}{"Count": len(dirtyRepos)}))
|
cli.Print("\n%s\n\n", i18n.T("cmd.dev.repos_with_changes", map[string]interface{}{"Count": len(dirtyRepos)}))
|
||||||
for _, s := range dirtyRepos {
|
for _, s := range dirtyRepos {
|
||||||
fmt.Printf(" %s: ", repoNameStyle.Render(s.Name))
|
cli.Print(" %s: ", repoNameStyle.Render(s.Name))
|
||||||
if s.Modified > 0 {
|
if s.Modified > 0 {
|
||||||
fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
|
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
|
||||||
}
|
}
|
||||||
if s.Untracked > 0 {
|
if s.Untracked > 0 {
|
||||||
fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
|
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
|
||||||
}
|
}
|
||||||
if s.Staged > 0 {
|
if s.Staged > 0 {
|
||||||
fmt.Printf("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
|
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm unless --all
|
// Confirm unless --all
|
||||||
if !all {
|
if !all {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
if !cli.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) {
|
if !cli.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) {
|
||||||
fmt.Println(i18n.T("cli.aborted"))
|
cli.Text(i18n.T("cli.aborted"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Commit each dirty repo
|
// Commit each dirty repo
|
||||||
var succeeded, failed int
|
var succeeded, failed int
|
||||||
for _, s := range dirtyRepos {
|
for _, s := range dirtyRepos {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.committing")), s.Name)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.committing")), s.Name)
|
||||||
|
|
||||||
if err := claudeCommit(ctx, s.Path, s.Name, registryPath); err != nil {
|
if err := claudeCommit(ctx, s.Path, s.Name, registryPath); err != nil {
|
||||||
fmt.Printf(" %s %s\n", errorStyle.Render("x"), err)
|
cli.Print(" %s %s\n", errorStyle.Render("x"), err)
|
||||||
failed++
|
failed++
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), i18n.T("cmd.dev.committed"))
|
cli.Print(" %s %s\n", successStyle.Render("v"), i18n.T("cmd.dev.committed"))
|
||||||
succeeded++
|
succeeded++
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
fmt.Printf("%s", successStyle.Render(i18n.T("cmd.dev.done_succeeded", map[string]interface{}{"Count": succeeded})))
|
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.done_succeeded", map[string]interface{}{"Count": succeeded})))
|
||||||
if failed > 0 {
|
if failed > 0 {
|
||||||
fmt.Printf(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
|
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -182,44 +180,44 @@ func runCommitSingleRepo(ctx context.Context, repoPath string, all bool) error {
|
||||||
if len(statuses) > 0 && statuses[0].Error != nil {
|
if len(statuses) > 0 && statuses[0].Error != nil {
|
||||||
return statuses[0].Error
|
return statuses[0].Error
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed to get repo status")
|
return cli.Err("failed to get repo status")
|
||||||
}
|
}
|
||||||
|
|
||||||
s := statuses[0]
|
s := statuses[0]
|
||||||
if !s.IsDirty() {
|
if !s.IsDirty() {
|
||||||
fmt.Println(i18n.T("cmd.dev.no_changes"))
|
cli.Text(i18n.T("cmd.dev.no_changes"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show status
|
// Show status
|
||||||
fmt.Printf("%s: ", repoNameStyle.Render(s.Name))
|
cli.Print("%s: ", repoNameStyle.Render(s.Name))
|
||||||
if s.Modified > 0 {
|
if s.Modified > 0 {
|
||||||
fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
|
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
|
||||||
}
|
}
|
||||||
if s.Untracked > 0 {
|
if s.Untracked > 0 {
|
||||||
fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
|
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
|
||||||
}
|
}
|
||||||
if s.Staged > 0 {
|
if s.Staged > 0 {
|
||||||
fmt.Printf("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
|
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Confirm unless --all
|
// Confirm unless --all
|
||||||
if !all {
|
if !all {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
if !cli.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) {
|
if !cli.Confirm(i18n.T("cmd.dev.confirm_claude_commit")) {
|
||||||
fmt.Println(i18n.T("cli.aborted"))
|
cli.Text(i18n.T("cli.aborted"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Commit
|
// Commit
|
||||||
if err := claudeCommit(ctx, repoPath, repoName, ""); err != nil {
|
if err := claudeCommit(ctx, repoPath, repoName, ""); err != nil {
|
||||||
fmt.Printf(" %s %s\n", errorStyle.Render("x"), err)
|
cli.Print(" %s %s\n", errorStyle.Render("x"), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), i18n.T("cmd.dev.committed"))
|
cli.Print(" %s %s\n", successStyle.Render("v"), i18n.T("cmd.dev.committed"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ package dev
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -57,8 +56,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddDevCommands registers the 'dev' command and all subcommands.
|
// AddDevCommands registers the 'dev' command and all subcommands.
|
||||||
func AddDevCommands(root *cobra.Command) {
|
func AddDevCommands(root *cli.Command) {
|
||||||
devCmd := &cobra.Command{
|
devCmd := &cli.Command{
|
||||||
Use: "dev",
|
Use: "dev",
|
||||||
Short: i18n.T("cmd.dev.short"),
|
Short: i18n.T("cmd.dev.short"),
|
||||||
Long: i18n.T("cmd.dev.long"),
|
Long: i18n.T("cmd.dev.long"),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
|
@ -10,7 +9,6 @@ import (
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Health command flags
|
// Health command flags
|
||||||
|
|
@ -20,12 +18,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addHealthCommand adds the 'health' command to the given parent command.
|
// addHealthCommand adds the 'health' command to the given parent command.
|
||||||
func addHealthCommand(parent *cobra.Command) {
|
func addHealthCommand(parent *cli.Command) {
|
||||||
healthCmd := &cobra.Command{
|
healthCmd := &cli.Command{
|
||||||
Use: "health",
|
Use: "health",
|
||||||
Short: i18n.T("cmd.dev.health.short"),
|
Short: i18n.T("cmd.dev.health.short"),
|
||||||
Long: i18n.T("cmd.dev.health.long"),
|
Long: i18n.T("cmd.dev.health.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runHealth(healthRegistryPath, healthVerbose)
|
return runHealth(healthRegistryPath, healthVerbose)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -46,21 +44,21 @@ func runHealth(registryPath string, verbose bool) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory
|
// Fallback: scan current directory
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to scan directory: %w", err)
|
return cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +75,7 @@ func runHealth(registryPath string, verbose bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.no_git_repos"))
|
cli.Text(i18n.T("cmd.dev.no_git_repos"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,25 +116,25 @@ func runHealth(registryPath string, verbose bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print summary line
|
// Print summary line
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
printHealthSummary(totalRepos, dirtyRepos, aheadRepos, behindRepos, errorRepos)
|
printHealthSummary(totalRepos, dirtyRepos, aheadRepos, behindRepos, errorRepos)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Verbose output
|
// Verbose output
|
||||||
if verbose {
|
if verbose {
|
||||||
if len(dirtyRepos) > 0 {
|
if len(dirtyRepos) > 0 {
|
||||||
fmt.Printf("%s %s\n", warningStyle.Render(i18n.T("cmd.dev.health.dirty_label")), formatRepoList(dirtyRepos))
|
cli.Print("%s %s\n", warningStyle.Render(i18n.T("cmd.dev.health.dirty_label")), formatRepoList(dirtyRepos))
|
||||||
}
|
}
|
||||||
if len(aheadRepos) > 0 {
|
if len(aheadRepos) > 0 {
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.dev.health.ahead_label")), formatRepoList(aheadRepos))
|
cli.Print("%s %s\n", successStyle.Render(i18n.T("cmd.dev.health.ahead_label")), formatRepoList(aheadRepos))
|
||||||
}
|
}
|
||||||
if len(behindRepos) > 0 {
|
if len(behindRepos) > 0 {
|
||||||
fmt.Printf("%s %s\n", warningStyle.Render(i18n.T("cmd.dev.health.behind_label")), formatRepoList(behindRepos))
|
cli.Print("%s %s\n", warningStyle.Render(i18n.T("cmd.dev.health.behind_label")), formatRepoList(behindRepos))
|
||||||
}
|
}
|
||||||
if len(errorRepos) > 0 {
|
if len(errorRepos) > 0 {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.T("cmd.dev.health.errors_label")), formatRepoList(errorRepos))
|
cli.Print("%s %s\n", errorStyle.Render(i18n.T("cmd.dev.health.errors_label")), formatRepoList(errorRepos))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -173,7 +171,7 @@ func printHealthSummary(total int, dirty, ahead, behind, errors []string) {
|
||||||
parts = append(parts, cli.StatusPart(len(errors), i18n.T("cmd.dev.health.errors"), cli.ErrorStyle))
|
parts = append(parts, cli.StatusPart(len(errors), i18n.T("cmd.dev.health.errors"), cli.ErrorStyle))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(cli.StatusLine(parts...))
|
cli.Text(cli.StatusLine(parts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatRepoList(reposList []string) string {
|
func formatRepoList(reposList []string) string {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Impact-specific styles (aliases to shared)
|
// Impact-specific styles (aliases to shared)
|
||||||
|
|
@ -22,13 +20,13 @@ var (
|
||||||
var impactRegistryPath string
|
var impactRegistryPath string
|
||||||
|
|
||||||
// addImpactCommand adds the 'impact' command to the given parent command.
|
// addImpactCommand adds the 'impact' command to the given parent command.
|
||||||
func addImpactCommand(parent *cobra.Command) {
|
func addImpactCommand(parent *cli.Command) {
|
||||||
impactCmd := &cobra.Command{
|
impactCmd := &cli.Command{
|
||||||
Use: "impact <repo-name>",
|
Use: "impact <repo-name>",
|
||||||
Short: i18n.T("cmd.dev.impact.short"),
|
Short: i18n.T("cmd.dev.impact.short"),
|
||||||
Long: i18n.T("cmd.dev.impact.long"),
|
Long: i18n.T("cmd.dev.impact.long"),
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runImpact(impactRegistryPath, args[0])
|
return runImpact(impactRegistryPath, args[0])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -46,14 +44,14 @@ func runImpact(registryPath string, repoName string) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return errors.New(i18n.T("cmd.dev.impact.requires_registry"))
|
return errors.New(i18n.T("cmd.dev.impact.requires_registry"))
|
||||||
|
|
@ -91,21 +89,21 @@ func runImpact(registryPath string, repoName string) error {
|
||||||
sort.Strings(indirect)
|
sort.Strings(indirect)
|
||||||
|
|
||||||
// Print results
|
// Print results
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.impact.analysis_for")), repoNameStyle.Render(repoName))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.impact.analysis_for")), repoNameStyle.Render(repoName))
|
||||||
if repo.Description != "" {
|
if repo.Description != "" {
|
||||||
fmt.Printf("%s\n", dimStyle.Render(repo.Description))
|
cli.Print("%s\n", dimStyle.Render(repo.Description))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if len(allAffected) == 0 {
|
if len(allAffected) == 0 {
|
||||||
fmt.Printf("%s %s\n", impactSafeStyle.Render("v"), i18n.T("cmd.dev.impact.no_dependents", map[string]interface{}{"Name": repoName}))
|
cli.Print("%s %s\n", impactSafeStyle.Render("v"), i18n.T("cmd.dev.impact.no_dependents", map[string]interface{}{"Name": repoName}))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct dependents
|
// Direct dependents
|
||||||
if len(direct) > 0 {
|
if len(direct) > 0 {
|
||||||
fmt.Printf("%s %s\n",
|
cli.Print("%s %s\n",
|
||||||
impactDirectStyle.Render("*"),
|
impactDirectStyle.Render("*"),
|
||||||
i18n.T("cmd.dev.impact.direct_dependents", map[string]interface{}{"Count": len(direct)}),
|
i18n.T("cmd.dev.impact.direct_dependents", map[string]interface{}{"Count": len(direct)}),
|
||||||
)
|
)
|
||||||
|
|
@ -115,14 +113,14 @@ func runImpact(registryPath string, repoName string) error {
|
||||||
if r != nil && r.Description != "" {
|
if r != nil && r.Description != "" {
|
||||||
desc = dimStyle.Render(" - " + cli.Truncate(r.Description, 40))
|
desc = dimStyle.Render(" - " + cli.Truncate(r.Description, 40))
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s%s\n", d, desc)
|
cli.Print(" %s%s\n", d, desc)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indirect dependents
|
// Indirect dependents
|
||||||
if len(indirect) > 0 {
|
if len(indirect) > 0 {
|
||||||
fmt.Printf("%s %s\n",
|
cli.Print("%s %s\n",
|
||||||
impactIndirectStyle.Render("o"),
|
impactIndirectStyle.Render("o"),
|
||||||
i18n.T("cmd.dev.impact.transitive_dependents", map[string]interface{}{"Count": len(indirect)}),
|
i18n.T("cmd.dev.impact.transitive_dependents", map[string]interface{}{"Count": len(indirect)}),
|
||||||
)
|
)
|
||||||
|
|
@ -132,13 +130,13 @@ func runImpact(registryPath string, repoName string) error {
|
||||||
if r != nil && r.Description != "" {
|
if r != nil && r.Description != "" {
|
||||||
desc = dimStyle.Render(" - " + cli.Truncate(r.Description, 40))
|
desc = dimStyle.Render(" - " + cli.Truncate(r.Description, 40))
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s%s\n", d, desc)
|
cli.Print(" %s%s\n", d, desc)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
fmt.Printf("%s %s\n",
|
cli.Print("%s %s\n",
|
||||||
dimStyle.Render(i18n.Label("summary")),
|
dimStyle.Render(i18n.Label("summary")),
|
||||||
i18n.T("cmd.dev.impact.changes_affect", map[string]interface{}{
|
i18n.T("cmd.dev.impact.changes_affect", map[string]interface{}{
|
||||||
"Repo": repoNameStyle.Render(repoName),
|
"Repo": repoNameStyle.Render(repoName),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package dev
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -13,7 +12,6 @@ import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Issue-specific styles (aliases to shared)
|
// Issue-specific styles (aliases to shared)
|
||||||
|
|
@ -59,12 +57,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addIssuesCommand adds the 'issues' command to the given parent command.
|
// addIssuesCommand adds the 'issues' command to the given parent command.
|
||||||
func addIssuesCommand(parent *cobra.Command) {
|
func addIssuesCommand(parent *cli.Command) {
|
||||||
issuesCmd := &cobra.Command{
|
issuesCmd := &cli.Command{
|
||||||
Use: "issues",
|
Use: "issues",
|
||||||
Short: i18n.T("cmd.dev.issues.short"),
|
Short: i18n.T("cmd.dev.issues.short"),
|
||||||
Long: i18n.T("cmd.dev.issues.long"),
|
Long: i18n.T("cmd.dev.issues.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
limit := issuesLimit
|
limit := issuesLimit
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
limit = 10
|
limit = 10
|
||||||
|
|
@ -93,21 +91,21 @@ func runIssues(registryPath string, limit int, assignee string) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory
|
// Fallback: scan current directory
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to scan directory: %w", err)
|
return cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,17 +116,17 @@ func runIssues(registryPath string, limit int, assignee string) error {
|
||||||
|
|
||||||
repoList := reg.List()
|
repoList := reg.List()
|
||||||
for i, repo := range repoList {
|
for i, repo := range repoList {
|
||||||
repoFullName := fmt.Sprintf("%s/%s", reg.Org, repo.Name)
|
repoFullName := cli.Sprintf("%s/%s", reg.Org, repo.Name)
|
||||||
fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("i18n.progress.fetch")), i+1, len(repoList), repo.Name)
|
cli.Print("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("i18n.progress.fetch")), i+1, len(repoList), repo.Name)
|
||||||
|
|
||||||
issues, err := fetchIssues(repoFullName, repo.Name, limit, assignee)
|
issues, err := fetchIssues(repoFullName, repo.Name, limit, assignee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fetchErrors = append(fetchErrors, fmt.Errorf("%s: %w", repo.Name, err))
|
fetchErrors = append(fetchErrors, cli.Wrap(err, repo.Name))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
allIssues = append(allIssues, issues...)
|
allIssues = append(allIssues, issues...)
|
||||||
}
|
}
|
||||||
fmt.Print("\033[2K\r") // Clear progress line
|
cli.Print("\033[2K\r") // Clear progress line
|
||||||
|
|
||||||
// Sort by created date (newest first)
|
// Sort by created date (newest first)
|
||||||
sort.Slice(allIssues, func(i, j int) bool {
|
sort.Slice(allIssues, func(i, j int) bool {
|
||||||
|
|
@ -137,11 +135,11 @@ func runIssues(registryPath string, limit int, assignee string) error {
|
||||||
|
|
||||||
// Print issues
|
// Print issues
|
||||||
if len(allIssues) == 0 {
|
if len(allIssues) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.issues.no_issues"))
|
cli.Text(i18n.T("cmd.dev.issues.no_issues"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.issues.open_issues", map[string]interface{}{"Count": len(allIssues)}))
|
cli.Print("\n%s\n\n", i18n.T("cmd.dev.issues.open_issues", map[string]interface{}{"Count": len(allIssues)}))
|
||||||
|
|
||||||
for _, issue := range allIssues {
|
for _, issue := range allIssues {
|
||||||
printIssue(issue)
|
printIssue(issue)
|
||||||
|
|
@ -149,9 +147,9 @@ func runIssues(registryPath string, limit int, assignee string) error {
|
||||||
|
|
||||||
// Print any errors
|
// Print any errors
|
||||||
if len(fetchErrors) > 0 {
|
if len(fetchErrors) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
for _, err := range fetchErrors {
|
for _, err := range fetchErrors {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.Label("error")), err)
|
cli.Print("%s %s\n", errorStyle.Render(i18n.Label("error")), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +161,7 @@ func fetchIssues(repoFullName, repoName string, limit int, assignee string) ([]G
|
||||||
"issue", "list",
|
"issue", "list",
|
||||||
"--repo", repoFullName,
|
"--repo", repoFullName,
|
||||||
"--state", "open",
|
"--state", "open",
|
||||||
"--limit", fmt.Sprintf("%d", limit),
|
"--limit", cli.Sprintf("%d", limit),
|
||||||
"--json", "number,title,state,createdAt,author,assignees,labels,url",
|
"--json", "number,title,state,createdAt,author,assignees,labels,url",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -180,7 +178,7 @@ func fetchIssues(repoFullName, repoName string, limit int, assignee string) ([]G
|
||||||
if strings.Contains(stderr, "no issues") || strings.Contains(stderr, "Could not resolve") {
|
if strings.Contains(stderr, "no issues") || strings.Contains(stderr, "Could not resolve") {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%s", stderr)
|
return nil, cli.Err("%s", stderr)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -200,11 +198,11 @@ func fetchIssues(repoFullName, repoName string, limit int, assignee string) ([]G
|
||||||
|
|
||||||
func printIssue(issue GitHubIssue) {
|
func printIssue(issue GitHubIssue) {
|
||||||
// #42 [core-bio] Fix avatar upload
|
// #42 [core-bio] Fix avatar upload
|
||||||
num := issueNumberStyle.Render(fmt.Sprintf("#%d", issue.Number))
|
num := issueNumberStyle.Render(cli.Sprintf("#%d", issue.Number))
|
||||||
repo := issueRepoStyle.Render(fmt.Sprintf("[%s]", issue.RepoName))
|
repo := issueRepoStyle.Render(cli.Sprintf("[%s]", issue.RepoName))
|
||||||
title := issueTitleStyle.Render(cli.Truncate(issue.Title, 60))
|
title := issueTitleStyle.Render(cli.Truncate(issue.Title, 60))
|
||||||
|
|
||||||
line := fmt.Sprintf(" %s %s %s", num, repo, title)
|
line := cli.Sprintf(" %s %s %s", num, repo, title)
|
||||||
|
|
||||||
// Add labels if any
|
// Add labels if any
|
||||||
if len(issue.Labels.Nodes) > 0 {
|
if len(issue.Labels.Nodes) > 0 {
|
||||||
|
|
@ -228,5 +226,5 @@ func printIssue(issue GitHubIssue) {
|
||||||
age := cli.FormatAge(issue.CreatedAt)
|
age := cli.FormatAge(issue.CreatedAt)
|
||||||
line += " " + issueAgeStyle.Render(age)
|
line += " " + issueAgeStyle.Render(age)
|
||||||
|
|
||||||
fmt.Println(line)
|
cli.Text(line)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pull command flags
|
// Pull command flags
|
||||||
|
|
@ -19,12 +18,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addPullCommand adds the 'pull' command to the given parent command.
|
// addPullCommand adds the 'pull' command to the given parent command.
|
||||||
func addPullCommand(parent *cobra.Command) {
|
func addPullCommand(parent *cli.Command) {
|
||||||
pullCmd := &cobra.Command{
|
pullCmd := &cli.Command{
|
||||||
Use: "pull",
|
Use: "pull",
|
||||||
Short: i18n.T("cmd.dev.pull.short"),
|
Short: i18n.T("cmd.dev.pull.short"),
|
||||||
Long: i18n.T("cmd.dev.pull.long"),
|
Long: i18n.T("cmd.dev.pull.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runPull(pullRegistryPath, pullAll)
|
return runPull(pullRegistryPath, pullAll)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -45,25 +44,25 @@ func runPull(registryPath string, all bool) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory
|
// Fallback: scan current directory
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to scan directory: %w", err)
|
return cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +78,7 @@ func runPull(registryPath string, all bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.no_git_repos"))
|
cli.Text(i18n.T("cmd.dev.no_git_repos"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,46 +100,46 @@ func runPull(registryPath string, all bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(toPull) == 0 {
|
if len(toPull) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.pull.all_up_to_date"))
|
cli.Text(i18n.T("cmd.dev.pull.all_up_to_date"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show what we're pulling
|
// Show what we're pulling
|
||||||
if all {
|
if all {
|
||||||
fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.pull.pulling_repos", map[string]interface{}{"Count": len(toPull)}))
|
cli.Print("\n%s\n\n", i18n.T("cmd.dev.pull.pulling_repos", map[string]interface{}{"Count": len(toPull)}))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s\n\n", i18n.T("cmd.dev.pull.repos_behind", map[string]interface{}{"Count": len(toPull)}))
|
cli.Print("\n%s\n\n", i18n.T("cmd.dev.pull.repos_behind", map[string]interface{}{"Count": len(toPull)}))
|
||||||
for _, s := range toPull {
|
for _, s := range toPull {
|
||||||
fmt.Printf(" %s: %s\n",
|
cli.Print(" %s: %s\n",
|
||||||
repoNameStyle.Render(s.Name),
|
repoNameStyle.Render(s.Name),
|
||||||
dimStyle.Render(i18n.T("cmd.dev.pull.commits_behind", map[string]interface{}{"Count": s.Behind})),
|
dimStyle.Render(i18n.T("cmd.dev.pull.commits_behind", map[string]interface{}{"Count": s.Behind})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull each repo
|
// Pull each repo
|
||||||
var succeeded, failed int
|
var succeeded, failed int
|
||||||
for _, s := range toPull {
|
for _, s := range toPull {
|
||||||
fmt.Printf(" %s %s... ", dimStyle.Render(i18n.T("cmd.dev.pull.pulling")), s.Name)
|
cli.Print(" %s %s... ", dimStyle.Render(i18n.T("cmd.dev.pull.pulling")), s.Name)
|
||||||
|
|
||||||
err := gitPull(ctx, s.Path)
|
err := gitPull(ctx, s.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s\n", errorStyle.Render("x "+err.Error()))
|
cli.Print("%s\n", errorStyle.Render("x "+err.Error()))
|
||||||
failed++
|
failed++
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s\n", successStyle.Render("v"))
|
cli.Print("%s\n", successStyle.Render("v"))
|
||||||
succeeded++
|
succeeded++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s", successStyle.Render(i18n.T("cmd.dev.pull.done_pulled", map[string]interface{}{"Count": succeeded})))
|
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.pull.done_pulled", map[string]interface{}{"Count": succeeded})))
|
||||||
if failed > 0 {
|
if failed > 0 {
|
||||||
fmt.Printf(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
|
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +149,7 @@ func gitPull(ctx context.Context, path string) error {
|
||||||
cmd.Dir = path
|
cmd.Dir = path
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s", string(output))
|
return cli.Err("%s", string(output))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
|
@ -10,7 +9,6 @@ import (
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Push command flags
|
// Push command flags
|
||||||
|
|
@ -20,12 +18,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addPushCommand adds the 'push' command to the given parent command.
|
// addPushCommand adds the 'push' command to the given parent command.
|
||||||
func addPushCommand(parent *cobra.Command) {
|
func addPushCommand(parent *cli.Command) {
|
||||||
pushCmd := &cobra.Command{
|
pushCmd := &cli.Command{
|
||||||
Use: "push",
|
Use: "push",
|
||||||
Short: i18n.T("cmd.dev.push.short"),
|
Short: i18n.T("cmd.dev.push.short"),
|
||||||
Long: i18n.T("cmd.dev.push.long"),
|
Long: i18n.T("cmd.dev.push.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runPush(pushRegistryPath, pushForce)
|
return runPush(pushRegistryPath, pushForce)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -52,24 +50,24 @@ func runPush(registryPath string, force bool) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory for repos
|
// Fallback: scan current directory for repos
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to scan directory: %w", err)
|
return cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +83,7 @@ func runPush(registryPath string, force bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.no_git_repos"))
|
cli.Text(i18n.T("cmd.dev.no_git_repos"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,15 +102,15 @@ func runPush(registryPath string, force bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(aheadRepos) == 0 {
|
if len(aheadRepos) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.push.all_up_to_date"))
|
cli.Text(i18n.T("cmd.dev.push.all_up_to_date"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show repos to push
|
// Show repos to push
|
||||||
fmt.Printf("\n%s\n\n", i18n.T("common.count.repos_unpushed", map[string]interface{}{"Count": len(aheadRepos)}))
|
cli.Print("\n%s\n\n", i18n.T("common.count.repos_unpushed", map[string]interface{}{"Count": len(aheadRepos)}))
|
||||||
totalCommits := 0
|
totalCommits := 0
|
||||||
for _, s := range aheadRepos {
|
for _, s := range aheadRepos {
|
||||||
fmt.Printf(" %s: %s\n",
|
cli.Print(" %s: %s\n",
|
||||||
repoNameStyle.Render(s.Name),
|
repoNameStyle.Render(s.Name),
|
||||||
aheadStyle.Render(i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead})),
|
aheadStyle.Render(i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead})),
|
||||||
)
|
)
|
||||||
|
|
@ -121,14 +119,14 @@ func runPush(registryPath string, force bool) error {
|
||||||
|
|
||||||
// Confirm unless --force
|
// Confirm unless --force
|
||||||
if !force {
|
if !force {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": totalCommits, "Repos": len(aheadRepos)})) {
|
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": totalCommits, "Repos": len(aheadRepos)})) {
|
||||||
fmt.Println(i18n.T("cli.aborted"))
|
cli.Text(i18n.T("cli.aborted"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Push sequentially (SSH passphrase needs interaction)
|
// Push sequentially (SSH passphrase needs interaction)
|
||||||
var pushPaths []string
|
var pushPaths []string
|
||||||
|
|
@ -143,15 +141,15 @@ func runPush(registryPath string, force bool) error {
|
||||||
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r.Success {
|
if r.Success {
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), r.Name)
|
cli.Print(" %s %s\n", successStyle.Render("v"), r.Name)
|
||||||
succeeded++
|
succeeded++
|
||||||
} else {
|
} else {
|
||||||
// Check if this is a non-fast-forward error (diverged branch)
|
// Check if this is a non-fast-forward error (diverged branch)
|
||||||
if git.IsNonFastForward(r.Error) {
|
if git.IsNonFastForward(r.Error) {
|
||||||
fmt.Printf(" %s %s: %s\n", warningStyle.Render("!"), r.Name, i18n.T("cmd.dev.push.diverged"))
|
cli.Print(" %s %s: %s\n", warningStyle.Render("!"), r.Name, i18n.T("cmd.dev.push.diverged"))
|
||||||
divergedRepos = append(divergedRepos, r)
|
divergedRepos = append(divergedRepos, r)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), r.Name, r.Error)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), r.Name, r.Error)
|
||||||
}
|
}
|
||||||
failed++
|
failed++
|
||||||
}
|
}
|
||||||
|
|
@ -159,22 +157,22 @@ func runPush(registryPath string, force bool) error {
|
||||||
|
|
||||||
// Handle diverged repos - offer to pull and retry
|
// Handle diverged repos - offer to pull and retry
|
||||||
if len(divergedRepos) > 0 {
|
if len(divergedRepos) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", i18n.T("cmd.dev.push.diverged_help"))
|
cli.Print("%s\n", i18n.T("cmd.dev.push.diverged_help"))
|
||||||
if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) {
|
if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
for _, r := range divergedRepos {
|
for _, r := range divergedRepos {
|
||||||
fmt.Printf(" %s %s...\n", dimStyle.Render("↓"), r.Name)
|
cli.Print(" %s %s...\n", dimStyle.Render("↓"), r.Name)
|
||||||
if err := git.Pull(ctx, r.Path); err != nil {
|
if err := git.Pull(ctx, r.Path); err != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), r.Name, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), r.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s...\n", dimStyle.Render("↑"), r.Name)
|
cli.Print(" %s %s...\n", dimStyle.Render("↑"), r.Name)
|
||||||
if err := git.Push(ctx, r.Path); err != nil {
|
if err := git.Push(ctx, r.Path); err != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), r.Name, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), r.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), r.Name)
|
cli.Print(" %s %s\n", successStyle.Render("v"), r.Name)
|
||||||
succeeded++
|
succeeded++
|
||||||
failed--
|
failed--
|
||||||
}
|
}
|
||||||
|
|
@ -182,12 +180,12 @@ func runPush(registryPath string, force bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s", successStyle.Render(i18n.T("cmd.dev.push.done_pushed", map[string]interface{}{"Count": succeeded})))
|
cli.Print("%s", successStyle.Render(i18n.T("cmd.dev.push.done_pushed", map[string]interface{}{"Count": succeeded})))
|
||||||
if failed > 0 {
|
if failed > 0 {
|
||||||
fmt.Printf(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
|
cli.Print(", %s", errorStyle.Render(i18n.T("common.count.failed", map[string]interface{}{"Count": failed})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +201,7 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error {
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(statuses) == 0 {
|
if len(statuses) == 0 {
|
||||||
return fmt.Errorf("failed to get repo status")
|
return cli.Err("failed to get repo status")
|
||||||
}
|
}
|
||||||
|
|
||||||
s := statuses[0]
|
s := statuses[0]
|
||||||
|
|
@ -214,20 +212,20 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error {
|
||||||
if !s.HasUnpushed() {
|
if !s.HasUnpushed() {
|
||||||
// Check if there are uncommitted changes
|
// Check if there are uncommitted changes
|
||||||
if s.IsDirty() {
|
if s.IsDirty() {
|
||||||
fmt.Printf("%s: ", repoNameStyle.Render(s.Name))
|
cli.Print("%s: ", repoNameStyle.Render(s.Name))
|
||||||
if s.Modified > 0 {
|
if s.Modified > 0 {
|
||||||
fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
|
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.modified", map[string]interface{}{"Count": s.Modified})))
|
||||||
}
|
}
|
||||||
if s.Untracked > 0 {
|
if s.Untracked > 0 {
|
||||||
fmt.Printf("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
|
cli.Print("%s ", dirtyStyle.Render(i18n.T("cmd.dev.untracked", map[string]interface{}{"Count": s.Untracked})))
|
||||||
}
|
}
|
||||||
if s.Staged > 0 {
|
if s.Staged > 0 {
|
||||||
fmt.Printf("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
|
cli.Print("%s ", aheadStyle.Render(i18n.T("cmd.dev.staged", map[string]interface{}{"Count": s.Staged})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
if cli.Confirm(i18n.T("cmd.dev.push.uncommitted_changes_commit")) {
|
if cli.Confirm(i18n.T("cmd.dev.push.uncommitted_changes_commit")) {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
// Use edit-enabled commit if only untracked files (may need .gitignore fix)
|
// Use edit-enabled commit if only untracked files (may need .gitignore fix)
|
||||||
var err error
|
var err error
|
||||||
if s.Modified == 0 && s.Staged == 0 && s.Untracked > 0 {
|
if s.Modified == 0 && s.Staged == 0 && s.Untracked > 0 {
|
||||||
|
|
@ -249,52 +247,52 @@ func runPushSingleRepo(ctx context.Context, repoPath string, force bool) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fmt.Println(i18n.T("cmd.dev.push.all_up_to_date"))
|
cli.Text(i18n.T("cmd.dev.push.all_up_to_date"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show commits to push
|
// Show commits to push
|
||||||
fmt.Printf("%s: %s\n", repoNameStyle.Render(s.Name),
|
cli.Print("%s: %s\n", repoNameStyle.Render(s.Name),
|
||||||
aheadStyle.Render(i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead})))
|
aheadStyle.Render(i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead})))
|
||||||
|
|
||||||
// Confirm unless --force
|
// Confirm unless --force
|
||||||
if !force {
|
if !force {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": s.Ahead, "Repos": 1})) {
|
if !cli.Confirm(i18n.T("cmd.dev.push.confirm_push", map[string]interface{}{"Commits": s.Ahead, "Repos": 1})) {
|
||||||
fmt.Println(i18n.T("cli.aborted"))
|
cli.Text(i18n.T("cli.aborted"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
err := git.Push(ctx, repoPath)
|
err := git.Push(ctx, repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsNonFastForward(err) {
|
if git.IsNonFastForward(err) {
|
||||||
fmt.Printf(" %s %s: %s\n", warningStyle.Render("!"), repoName, i18n.T("cmd.dev.push.diverged"))
|
cli.Print(" %s %s: %s\n", warningStyle.Render("!"), repoName, i18n.T("cmd.dev.push.diverged"))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", i18n.T("cmd.dev.push.diverged_help"))
|
cli.Print("%s\n", i18n.T("cmd.dev.push.diverged_help"))
|
||||||
if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) {
|
if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf(" %s %s...\n", dimStyle.Render("↓"), repoName)
|
cli.Print(" %s %s...\n", dimStyle.Render("↓"), repoName)
|
||||||
if pullErr := git.Pull(ctx, repoPath); pullErr != nil {
|
if pullErr := git.Pull(ctx, repoPath); pullErr != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), repoName, pullErr)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), repoName, pullErr)
|
||||||
return pullErr
|
return pullErr
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s...\n", dimStyle.Render("↑"), repoName)
|
cli.Print(" %s %s...\n", dimStyle.Render("↑"), repoName)
|
||||||
if pushErr := git.Push(ctx, repoPath); pushErr != nil {
|
if pushErr := git.Push(ctx, repoPath); pushErr != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), repoName, pushErr)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), repoName, pushErr)
|
||||||
return pushErr
|
return pushErr
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), repoName)
|
cli.Print(" %s %s\n", successStyle.Render("v"), repoName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), repoName, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), repoName, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), repoName)
|
cli.Print(" %s %s\n", successStyle.Render("v"), repoName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package dev
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -13,7 +12,6 @@ import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PR-specific styles (aliases to shared)
|
// PR-specific styles (aliases to shared)
|
||||||
|
|
@ -60,12 +58,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addReviewsCommand adds the 'reviews' command to the given parent command.
|
// addReviewsCommand adds the 'reviews' command to the given parent command.
|
||||||
func addReviewsCommand(parent *cobra.Command) {
|
func addReviewsCommand(parent *cli.Command) {
|
||||||
reviewsCmd := &cobra.Command{
|
reviewsCmd := &cli.Command{
|
||||||
Use: "reviews",
|
Use: "reviews",
|
||||||
Short: i18n.T("cmd.dev.reviews.short"),
|
Short: i18n.T("cmd.dev.reviews.short"),
|
||||||
Long: i18n.T("cmd.dev.reviews.long"),
|
Long: i18n.T("cmd.dev.reviews.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runReviews(reviewsRegistryPath, reviewsAuthor, reviewsShowAll)
|
return runReviews(reviewsRegistryPath, reviewsAuthor, reviewsShowAll)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -90,21 +88,21 @@ func runReviews(registryPath string, author string, showAll bool) error {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load registry: %w", err)
|
return cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory
|
// Fallback: scan current directory
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to scan directory: %w", err)
|
return cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -115,12 +113,12 @@ func runReviews(registryPath string, author string, showAll bool) error {
|
||||||
|
|
||||||
repoList := reg.List()
|
repoList := reg.List()
|
||||||
for i, repo := range repoList {
|
for i, repo := range repoList {
|
||||||
repoFullName := fmt.Sprintf("%s/%s", reg.Org, repo.Name)
|
repoFullName := cli.Sprintf("%s/%s", reg.Org, repo.Name)
|
||||||
fmt.Printf("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("i18n.progress.fetch")), i+1, len(repoList), repo.Name)
|
cli.Print("\033[2K\r%s %d/%d %s", dimStyle.Render(i18n.T("i18n.progress.fetch")), i+1, len(repoList), repo.Name)
|
||||||
|
|
||||||
prs, err := fetchPRs(repoFullName, repo.Name, author)
|
prs, err := fetchPRs(repoFullName, repo.Name, author)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fetchErrors = append(fetchErrors, fmt.Errorf("%s: %w", repo.Name, err))
|
fetchErrors = append(fetchErrors, cli.Wrap(err, repo.Name))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +130,7 @@ func runReviews(registryPath string, author string, showAll bool) error {
|
||||||
allPRs = append(allPRs, pr)
|
allPRs = append(allPRs, pr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Print("\033[2K\r") // Clear progress line
|
cli.Print("\033[2K\r") // Clear progress line
|
||||||
|
|
||||||
// Sort: pending review first, then by date
|
// Sort: pending review first, then by date
|
||||||
sort.Slice(allPRs, func(i, j int) bool {
|
sort.Slice(allPRs, func(i, j int) bool {
|
||||||
|
|
@ -147,7 +145,7 @@ func runReviews(registryPath string, author string, showAll bool) error {
|
||||||
|
|
||||||
// Print PRs
|
// Print PRs
|
||||||
if len(allPRs) == 0 {
|
if len(allPRs) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.reviews.no_prs"))
|
cli.Text(i18n.T("cmd.dev.reviews.no_prs"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,19 +162,19 @@ func runReviews(registryPath string, author string, showAll bool) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s", i18n.T("cmd.dev.reviews.open_prs", map[string]interface{}{"Count": len(allPRs)}))
|
cli.Print("%s", i18n.T("cmd.dev.reviews.open_prs", map[string]interface{}{"Count": len(allPRs)}))
|
||||||
if pending > 0 {
|
if pending > 0 {
|
||||||
fmt.Printf(" * %s", prPendingStyle.Render(i18n.T("common.count.pending", map[string]interface{}{"Count": pending})))
|
cli.Print(" * %s", prPendingStyle.Render(i18n.T("common.count.pending", map[string]interface{}{"Count": pending})))
|
||||||
}
|
}
|
||||||
if approved > 0 {
|
if approved > 0 {
|
||||||
fmt.Printf(" * %s", prApprovedStyle.Render(i18n.T("cmd.dev.reviews.approved", map[string]interface{}{"Count": approved})))
|
cli.Print(" * %s", prApprovedStyle.Render(i18n.T("cmd.dev.reviews.approved", map[string]interface{}{"Count": approved})))
|
||||||
}
|
}
|
||||||
if changesRequested > 0 {
|
if changesRequested > 0 {
|
||||||
fmt.Printf(" * %s", prChangesStyle.Render(i18n.T("cmd.dev.reviews.changes_requested", map[string]interface{}{"Count": changesRequested})))
|
cli.Print(" * %s", prChangesStyle.Render(i18n.T("cmd.dev.reviews.changes_requested", map[string]interface{}{"Count": changesRequested})))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
for _, pr := range allPRs {
|
for _, pr := range allPRs {
|
||||||
printPR(pr)
|
printPR(pr)
|
||||||
|
|
@ -184,9 +182,9 @@ func runReviews(registryPath string, author string, showAll bool) error {
|
||||||
|
|
||||||
// Print any errors
|
// Print any errors
|
||||||
if len(fetchErrors) > 0 {
|
if len(fetchErrors) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
for _, err := range fetchErrors {
|
for _, err := range fetchErrors {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.Label("error")), err)
|
cli.Print("%s %s\n", errorStyle.Render(i18n.Label("error")), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,7 +211,7 @@ func fetchPRs(repoFullName, repoName string, author string) ([]GitHubPR, error)
|
||||||
if strings.Contains(stderr, "no pull requests") || strings.Contains(stderr, "Could not resolve") {
|
if strings.Contains(stderr, "no pull requests") || strings.Contains(stderr, "Could not resolve") {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%s", stderr)
|
return nil, cli.Err("%s", stderr)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -233,8 +231,8 @@ func fetchPRs(repoFullName, repoName string, author string) ([]GitHubPR, error)
|
||||||
|
|
||||||
func printPR(pr GitHubPR) {
|
func printPR(pr GitHubPR) {
|
||||||
// #12 [core-php] Webhook validation
|
// #12 [core-php] Webhook validation
|
||||||
num := prNumberStyle.Render(fmt.Sprintf("#%d", pr.Number))
|
num := prNumberStyle.Render(cli.Sprintf("#%d", pr.Number))
|
||||||
repo := issueRepoStyle.Render(fmt.Sprintf("[%s]", pr.RepoName))
|
repo := issueRepoStyle.Render(cli.Sprintf("[%s]", pr.RepoName))
|
||||||
title := prTitleStyle.Render(cli.Truncate(pr.Title, 50))
|
title := prTitleStyle.Render(cli.Truncate(pr.Title, 50))
|
||||||
author := prAuthorStyle.Render("@" + pr.Author.Login)
|
author := prAuthorStyle.Render("@" + pr.Author.Login)
|
||||||
|
|
||||||
|
|
@ -257,5 +255,5 @@ func printPR(pr GitHubPR) {
|
||||||
|
|
||||||
age := cli.FormatAge(pr.CreatedAt)
|
age := cli.FormatAge(pr.CreatedAt)
|
||||||
|
|
||||||
fmt.Printf(" %s %s %s%s %s %s %s\n", num, repo, title, draft, author, status, issueAgeStyle.Render(age))
|
cli.Print(" %s %s %s%s %s %s %s\n", num, repo, title, draft, author, status, issueAgeStyle.Render(age))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
@ -10,23 +9,23 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addSyncCommand adds the 'sync' command to the given parent command.
|
// addSyncCommand adds the 'sync' command to the given parent command.
|
||||||
func addSyncCommand(parent *cobra.Command) {
|
func addSyncCommand(parent *cli.Command) {
|
||||||
syncCmd := &cobra.Command{
|
syncCmd := &cli.Command{
|
||||||
Use: "sync",
|
Use: "sync",
|
||||||
Short: i18n.T("cmd.dev.sync.short"),
|
Short: i18n.T("cmd.dev.sync.short"),
|
||||||
Long: i18n.T("cmd.dev.sync.long"),
|
Long: i18n.T("cmd.dev.sync.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
if err := runSync(); err != nil {
|
if err := runSync(); err != nil {
|
||||||
return fmt.Errorf("%s %w", i18n.Label("error"), err)
|
return cli.Wrap(err, i18n.Label("error"))
|
||||||
}
|
}
|
||||||
fmt.Println(i18n.T("i18n.done.sync", "public APIs"))
|
cli.Text(i18n.T("i18n.done.sync", "public APIs"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +42,7 @@ func runSync() error {
|
||||||
pkgDir := "pkg"
|
pkgDir := "pkg"
|
||||||
internalDirs, err := os.ReadDir(pkgDir)
|
internalDirs, err := os.ReadDir(pkgDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read pkg directory: %w", err)
|
return cli.Wrap(err, "failed to read pkg directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dir := range internalDirs {
|
for _, dir := range internalDirs {
|
||||||
|
|
@ -62,11 +61,11 @@ func runSync() error {
|
||||||
|
|
||||||
symbols, err := getExportedSymbols(internalFile)
|
symbols, err := getExportedSymbols(internalFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error getting symbols for service '%s': %w", serviceName, err)
|
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := generatePublicAPIFile(publicDir, publicFile, serviceName, symbols); err != nil {
|
if err := generatePublicAPIFile(publicDir, publicFile, serviceName, symbols); err != nil {
|
||||||
return fmt.Errorf("error generating public API file for service '%s': %w", serviceName, err)
|
return cli.Wrap(err, cli.Sprintf("error generating public API file for service '%s'", serviceName))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,17 @@ package dev
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/devops"
|
"github.com/host-uk/core/pkg/devops"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMCommands adds the dev environment VM commands to the dev parent command.
|
// addVMCommands adds the dev environment VM commands to the dev parent command.
|
||||||
// These are added as direct subcommands: core dev install, core dev boot, etc.
|
// These are added as direct subcommands: core dev install, core dev boot, etc.
|
||||||
func addVMCommands(parent *cobra.Command) {
|
func addVMCommands(parent *cli.Command) {
|
||||||
addVMInstallCommand(parent)
|
addVMInstallCommand(parent)
|
||||||
addVMBootCommand(parent)
|
addVMBootCommand(parent)
|
||||||
addVMStopCommand(parent)
|
addVMStopCommand(parent)
|
||||||
|
|
@ -27,12 +26,12 @@ func addVMCommands(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMInstallCommand adds the 'dev install' command.
|
// addVMInstallCommand adds the 'dev install' command.
|
||||||
func addVMInstallCommand(parent *cobra.Command) {
|
func addVMInstallCommand(parent *cli.Command) {
|
||||||
installCmd := &cobra.Command{
|
installCmd := &cli.Command{
|
||||||
Use: "install",
|
Use: "install",
|
||||||
Short: i18n.T("cmd.dev.vm.install.short"),
|
Short: i18n.T("cmd.dev.vm.install.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.install.long"),
|
Long: i18n.T("cmd.dev.vm.install.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMInstall()
|
return runVMInstall()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -47,16 +46,16 @@ func runVMInstall() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.IsInstalled() {
|
if d.IsInstalled() {
|
||||||
fmt.Println(successStyle.Render(i18n.T("cmd.dev.vm.already_installed")))
|
cli.Text(successStyle.Render(i18n.T("cmd.dev.vm.already_installed")))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.check_updates", map[string]interface{}{"Command": dimStyle.Render("core dev update")}))
|
cli.Text(i18n.T("cmd.dev.vm.check_updates", map[string]interface{}{"Command": dimStyle.Render("core dev update")}))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("image")), devops.ImageName())
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("image")), devops.ImageName())
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.downloading"))
|
cli.Text(i18n.T("cmd.dev.vm.downloading"))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
@ -66,23 +65,23 @@ func runVMInstall() error {
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
pct := int(float64(downloaded) / float64(total) * 100)
|
pct := int(float64(downloaded) / float64(total) * 100)
|
||||||
if pct != int(float64(lastProgress)/float64(total)*100) {
|
if pct != int(float64(lastProgress)/float64(total)*100) {
|
||||||
fmt.Printf("\r%s %d%%", dimStyle.Render(i18n.T("cmd.dev.vm.progress_label")), pct)
|
cli.Print("\r%s %d%%", dimStyle.Render(i18n.T("cmd.dev.vm.progress_label")), pct)
|
||||||
lastProgress = downloaded
|
lastProgress = downloaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Println() // Clear progress line
|
cli.Line("") // Clear progress line
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("install failed: %w", err)
|
return cli.Wrap(err, "install failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Since(start).Round(time.Second)
|
elapsed := time.Since(start).Round(time.Second)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.installed_in", map[string]interface{}{"Duration": elapsed}))
|
cli.Text(i18n.T("cmd.dev.vm.installed_in", map[string]interface{}{"Duration": elapsed}))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")}))
|
cli.Text(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")}))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -95,12 +94,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMBootCommand adds the 'devops boot' command.
|
// addVMBootCommand adds the 'devops boot' command.
|
||||||
func addVMBootCommand(parent *cobra.Command) {
|
func addVMBootCommand(parent *cli.Command) {
|
||||||
bootCmd := &cobra.Command{
|
bootCmd := &cli.Command{
|
||||||
Use: "boot",
|
Use: "boot",
|
||||||
Short: i18n.T("cmd.dev.vm.boot.short"),
|
Short: i18n.T("cmd.dev.vm.boot.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.boot.long"),
|
Long: i18n.T("cmd.dev.vm.boot.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMBoot(vmBootMemory, vmBootCPUs, vmBootFresh)
|
return runVMBoot(vmBootMemory, vmBootCPUs, vmBootFresh)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -131,31 +130,31 @@ func runVMBoot(memory, cpus int, fresh bool) error {
|
||||||
}
|
}
|
||||||
opts.Fresh = fresh
|
opts.Fresh = fresh
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.config_label")), i18n.T("cmd.dev.vm.config_value", map[string]interface{}{"Memory": opts.Memory, "CPUs": opts.CPUs}))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.config_label")), i18n.T("cmd.dev.vm.config_value", map[string]interface{}{"Memory": opts.Memory, "CPUs": opts.CPUs}))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.booting"))
|
cli.Text(i18n.T("cmd.dev.vm.booting"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := d.Boot(ctx, opts); err != nil {
|
if err := d.Boot(ctx, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(successStyle.Render(i18n.T("cmd.dev.vm.running")))
|
cli.Text(successStyle.Render(i18n.T("cmd.dev.vm.running")))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.connect_with", map[string]interface{}{"Command": dimStyle.Render("core dev shell")}))
|
cli.Text(i18n.T("cmd.dev.vm.connect_with", map[string]interface{}{"Command": dimStyle.Render("core dev shell")}))
|
||||||
fmt.Printf("%s %s\n", i18n.T("cmd.dev.vm.ssh_port"), dimStyle.Render("2222"))
|
cli.Print("%s %s\n", i18n.T("cmd.dev.vm.ssh_port"), dimStyle.Render("2222"))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMStopCommand adds the 'devops stop' command.
|
// addVMStopCommand adds the 'devops stop' command.
|
||||||
func addVMStopCommand(parent *cobra.Command) {
|
func addVMStopCommand(parent *cli.Command) {
|
||||||
stopCmd := &cobra.Command{
|
stopCmd := &cli.Command{
|
||||||
Use: "stop",
|
Use: "stop",
|
||||||
Short: i18n.T("cmd.dev.vm.stop.short"),
|
Short: i18n.T("cmd.dev.vm.stop.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.stop.long"),
|
Long: i18n.T("cmd.dev.vm.stop.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMStop()
|
return runVMStop()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -176,27 +175,27 @@ func runVMStop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !running {
|
if !running {
|
||||||
fmt.Println(dimStyle.Render(i18n.T("cmd.dev.vm.not_running")))
|
cli.Text(dimStyle.Render(i18n.T("cmd.dev.vm.not_running")))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.stopping"))
|
cli.Text(i18n.T("cmd.dev.vm.stopping"))
|
||||||
|
|
||||||
if err := d.Stop(ctx); err != nil {
|
if err := d.Stop(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(successStyle.Render(i18n.T("common.status.stopped")))
|
cli.Text(successStyle.Render(i18n.T("common.status.stopped")))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMStatusCommand adds the 'devops status' command.
|
// addVMStatusCommand adds the 'devops status' command.
|
||||||
func addVMStatusCommand(parent *cobra.Command) {
|
func addVMStatusCommand(parent *cli.Command) {
|
||||||
statusCmd := &cobra.Command{
|
statusCmd := &cli.Command{
|
||||||
Use: "vm-status",
|
Use: "vm-status",
|
||||||
Short: i18n.T("cmd.dev.vm.status.short"),
|
Short: i18n.T("cmd.dev.vm.status.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.status.long"),
|
Long: i18n.T("cmd.dev.vm.status.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMStatus()
|
return runVMStatus()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -216,36 +215,36 @@ func runVMStatus() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(headerStyle.Render(i18n.T("cmd.dev.vm.status_title")))
|
cli.Text(headerStyle.Render(i18n.T("cmd.dev.vm.status_title")))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Installation status
|
// Installation status
|
||||||
if status.Installed {
|
if status.Installed {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.installed_label")), successStyle.Render(i18n.T("cmd.dev.vm.installed_yes")))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.installed_label")), successStyle.Render(i18n.T("cmd.dev.vm.installed_yes")))
|
||||||
if status.ImageVersion != "" {
|
if status.ImageVersion != "" {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("version")), status.ImageVersion)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("version")), status.ImageVersion)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.installed_label")), errorStyle.Render(i18n.T("cmd.dev.vm.installed_no")))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.installed_label")), errorStyle.Render(i18n.T("cmd.dev.vm.installed_no")))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.install_with", map[string]interface{}{"Command": dimStyle.Render("core dev install")}))
|
cli.Text(i18n.T("cmd.dev.vm.install_with", map[string]interface{}{"Command": dimStyle.Render("core dev install")}))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Running status
|
// Running status
|
||||||
if status.Running {
|
if status.Running {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("status")), successStyle.Render(i18n.T("common.status.running")))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("status")), successStyle.Render(i18n.T("common.status.running")))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.container_label")), status.ContainerID[:8])
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.container_label")), status.ContainerID[:8])
|
||||||
fmt.Printf("%s %dMB\n", dimStyle.Render(i18n.T("cmd.dev.vm.memory_label")), status.Memory)
|
cli.Print("%s %dMB\n", dimStyle.Render(i18n.T("cmd.dev.vm.memory_label")), status.Memory)
|
||||||
fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.dev.vm.cpus_label")), status.CPUs)
|
cli.Print("%s %d\n", dimStyle.Render(i18n.T("cmd.dev.vm.cpus_label")), status.CPUs)
|
||||||
fmt.Printf("%s %d\n", dimStyle.Render(i18n.T("cmd.dev.vm.ssh_port")), status.SSHPort)
|
cli.Print("%s %d\n", dimStyle.Render(i18n.T("cmd.dev.vm.ssh_port")), status.SSHPort)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.uptime_label")), formatVMUptime(status.Uptime))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.uptime_label")), formatVMUptime(status.Uptime))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("status")), dimStyle.Render(i18n.T("common.status.stopped")))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("status")), dimStyle.Render(i18n.T("common.status.stopped")))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")}))
|
cli.Text(i18n.T("cmd.dev.vm.start_with", map[string]interface{}{"Command": dimStyle.Render("core dev boot")}))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -253,27 +252,27 @@ func runVMStatus() error {
|
||||||
|
|
||||||
func formatVMUptime(d time.Duration) string {
|
func formatVMUptime(d time.Duration) string {
|
||||||
if d < time.Minute {
|
if d < time.Minute {
|
||||||
return fmt.Sprintf("%ds", int(d.Seconds()))
|
return cli.Sprintf("%ds", int(d.Seconds()))
|
||||||
}
|
}
|
||||||
if d < time.Hour {
|
if d < time.Hour {
|
||||||
return fmt.Sprintf("%dm", int(d.Minutes()))
|
return cli.Sprintf("%dm", int(d.Minutes()))
|
||||||
}
|
}
|
||||||
if d < 24*time.Hour {
|
if d < 24*time.Hour {
|
||||||
return fmt.Sprintf("%dh %dm", int(d.Hours()), int(d.Minutes())%60)
|
return cli.Sprintf("%dh %dm", int(d.Hours()), int(d.Minutes())%60)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%dd %dh", int(d.Hours()/24), int(d.Hours())%24)
|
return cli.Sprintf("%dd %dh", int(d.Hours()/24), int(d.Hours())%24)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VM shell command flags
|
// VM shell command flags
|
||||||
var vmShellConsole bool
|
var vmShellConsole bool
|
||||||
|
|
||||||
// addVMShellCommand adds the 'devops shell' command.
|
// addVMShellCommand adds the 'devops shell' command.
|
||||||
func addVMShellCommand(parent *cobra.Command) {
|
func addVMShellCommand(parent *cli.Command) {
|
||||||
shellCmd := &cobra.Command{
|
shellCmd := &cli.Command{
|
||||||
Use: "shell [-- command...]",
|
Use: "shell [-- command...]",
|
||||||
Short: i18n.T("cmd.dev.vm.shell.short"),
|
Short: i18n.T("cmd.dev.vm.shell.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.shell.long"),
|
Long: i18n.T("cmd.dev.vm.shell.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMShell(vmShellConsole, args)
|
return runVMShell(vmShellConsole, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -305,12 +304,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMServeCommand adds the 'devops serve' command.
|
// addVMServeCommand adds the 'devops serve' command.
|
||||||
func addVMServeCommand(parent *cobra.Command) {
|
func addVMServeCommand(parent *cli.Command) {
|
||||||
serveCmd := &cobra.Command{
|
serveCmd := &cli.Command{
|
||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: i18n.T("cmd.dev.vm.serve.short"),
|
Short: i18n.T("cmd.dev.vm.serve.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.serve.long"),
|
Long: i18n.T("cmd.dev.vm.serve.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMServe(vmServePort, vmServePath)
|
return runVMServe(vmServePort, vmServePath)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -345,12 +344,12 @@ func runVMServe(port int, path string) error {
|
||||||
var vmTestName string
|
var vmTestName string
|
||||||
|
|
||||||
// addVMTestCommand adds the 'devops test' command.
|
// addVMTestCommand adds the 'devops test' command.
|
||||||
func addVMTestCommand(parent *cobra.Command) {
|
func addVMTestCommand(parent *cli.Command) {
|
||||||
testCmd := &cobra.Command{
|
testCmd := &cli.Command{
|
||||||
Use: "test [-- command...]",
|
Use: "test [-- command...]",
|
||||||
Short: i18n.T("cmd.dev.vm.test.short"),
|
Short: i18n.T("cmd.dev.vm.test.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.test.long"),
|
Long: i18n.T("cmd.dev.vm.test.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMTest(vmTestName, args)
|
return runVMTest(vmTestName, args)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -388,12 +387,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMClaudeCommand adds the 'devops claude' command.
|
// addVMClaudeCommand adds the 'devops claude' command.
|
||||||
func addVMClaudeCommand(parent *cobra.Command) {
|
func addVMClaudeCommand(parent *cli.Command) {
|
||||||
claudeCmd := &cobra.Command{
|
claudeCmd := &cli.Command{
|
||||||
Use: "claude",
|
Use: "claude",
|
||||||
Short: i18n.T("cmd.dev.vm.claude.short"),
|
Short: i18n.T("cmd.dev.vm.claude.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.claude.long"),
|
Long: i18n.T("cmd.dev.vm.claude.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMClaude(vmClaudeNoAuth, vmClaudeModel, vmClaudeAuthFlags)
|
return runVMClaude(vmClaudeNoAuth, vmClaudeModel, vmClaudeAuthFlags)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -430,12 +429,12 @@ func runVMClaude(noAuth bool, model string, authFlags []string) error {
|
||||||
var vmUpdateApply bool
|
var vmUpdateApply bool
|
||||||
|
|
||||||
// addVMUpdateCommand adds the 'devops update' command.
|
// addVMUpdateCommand adds the 'devops update' command.
|
||||||
func addVMUpdateCommand(parent *cobra.Command) {
|
func addVMUpdateCommand(parent *cli.Command) {
|
||||||
updateCmd := &cobra.Command{
|
updateCmd := &cli.Command{
|
||||||
Use: "update",
|
Use: "update",
|
||||||
Short: i18n.T("cmd.dev.vm.update.short"),
|
Short: i18n.T("cmd.dev.vm.update.short"),
|
||||||
Long: i18n.T("cmd.dev.vm.update.long"),
|
Long: i18n.T("cmd.dev.vm.update.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runVMUpdate(vmUpdateApply)
|
return runVMUpdate(vmUpdateApply)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -453,58 +452,58 @@ func runVMUpdate(apply bool) error {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
fmt.Println(i18n.T("common.progress.checking_updates"))
|
cli.Text(i18n.T("common.progress.checking_updates"))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
current, latest, hasUpdate, err := d.CheckUpdate(ctx)
|
current, latest, hasUpdate, err := d.CheckUpdate(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check for updates: %w", err)
|
return cli.Wrap(err, "failed to check for updates")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("current")), valueStyle.Render(current))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("current")), valueStyle.Render(current))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.latest_label")), valueStyle.Render(latest))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.dev.vm.latest_label")), valueStyle.Render(latest))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if !hasUpdate {
|
if !hasUpdate {
|
||||||
fmt.Println(successStyle.Render(i18n.T("cmd.dev.vm.up_to_date")))
|
cli.Text(successStyle.Render(i18n.T("cmd.dev.vm.up_to_date")))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(warningStyle.Render(i18n.T("cmd.dev.vm.update_available")))
|
cli.Text(warningStyle.Render(i18n.T("cmd.dev.vm.update_available")))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if !apply {
|
if !apply {
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.run_to_update", map[string]interface{}{"Command": dimStyle.Render("core dev update --apply")}))
|
cli.Text(i18n.T("cmd.dev.vm.run_to_update", map[string]interface{}{"Command": dimStyle.Render("core dev update --apply")}))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop if running
|
// Stop if running
|
||||||
running, _ := d.IsRunning(ctx)
|
running, _ := d.IsRunning(ctx)
|
||||||
if running {
|
if running {
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.stopping_current"))
|
cli.Text(i18n.T("cmd.dev.vm.stopping_current"))
|
||||||
_ = d.Stop(ctx)
|
_ = d.Stop(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.downloading_update"))
|
cli.Text(i18n.T("cmd.dev.vm.downloading_update"))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err = d.Install(ctx, func(downloaded, total int64) {
|
err = d.Install(ctx, func(downloaded, total int64) {
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
pct := int(float64(downloaded) / float64(total) * 100)
|
pct := int(float64(downloaded) / float64(total) * 100)
|
||||||
fmt.Printf("\r%s %d%%", dimStyle.Render(i18n.T("cmd.dev.vm.progress_label")), pct)
|
cli.Print("\r%s %d%%", dimStyle.Render(i18n.T("cmd.dev.vm.progress_label")), pct)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("update failed: %w", err)
|
return cli.Wrap(err, "update failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Since(start).Round(time.Second)
|
elapsed := time.Since(start).Round(time.Second)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.vm.updated_in", map[string]interface{}{"Duration": elapsed}))
|
cli.Text(i18n.T("cmd.dev.vm.updated_in", map[string]interface{}{"Duration": elapsed}))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -13,7 +12,6 @@ import (
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Work command flags
|
// Work command flags
|
||||||
|
|
@ -24,12 +22,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// addWorkCommand adds the 'work' command to the given parent command.
|
// addWorkCommand adds the 'work' command to the given parent command.
|
||||||
func addWorkCommand(parent *cobra.Command) {
|
func addWorkCommand(parent *cli.Command) {
|
||||||
workCmd := &cobra.Command{
|
workCmd := &cli.Command{
|
||||||
Use: "work",
|
Use: "work",
|
||||||
Short: i18n.T("cmd.dev.work.short"),
|
Short: i18n.T("cmd.dev.work.short"),
|
||||||
Long: i18n.T("cmd.dev.work.long"),
|
Long: i18n.T("cmd.dev.work.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runWork(workRegistryPath, workStatusOnly, workAutoCommit)
|
return runWork(workRegistryPath, workStatusOnly, workAutoCommit)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +63,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.dev.no_git_repos"))
|
cli.Text(i18n.T("cmd.dev.no_git_repos"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +73,7 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
||||||
Names: names,
|
Names: names,
|
||||||
})
|
})
|
||||||
if !handled {
|
if !handled {
|
||||||
return fmt.Errorf("git service not available")
|
return cli.Err("git service not available")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -108,9 +106,9 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
||||||
|
|
||||||
// Auto-commit dirty repos if requested
|
// Auto-commit dirty repos if requested
|
||||||
if autoCommit && len(dirtyRepos) > 0 {
|
if autoCommit && len(dirtyRepos) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", cli.TitleStyle.Render(i18n.T("cmd.dev.commit.committing")))
|
cli.Print("%s\n", cli.TitleStyle.Render(i18n.T("cmd.dev.commit.committing")))
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
for _, s := range dirtyRepos {
|
for _, s := range dirtyRepos {
|
||||||
// PERFORM commit via agentic service
|
// PERFORM commit via agentic service
|
||||||
|
|
@ -119,13 +117,13 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
})
|
})
|
||||||
if !handled {
|
if !handled {
|
||||||
fmt.Printf(" %s %s: %s\n", warningStyle.Render("!"), s.Name, "agentic service not available")
|
cli.Print(" %s %s: %s\n", warningStyle.Render("!"), s.Name, "agentic service not available")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), s.Name)
|
cli.Print(" %s %s\n", successStyle.Render("v"), s.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,32 +146,32 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
||||||
// If status only, we're done
|
// If status only, we're done
|
||||||
if statusOnly {
|
if statusOnly {
|
||||||
if len(dirtyRepos) > 0 && !autoCommit {
|
if len(dirtyRepos) > 0 && !autoCommit {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.dev.work.use_commit_flag")))
|
cli.Print("%s\n", dimStyle.Render(i18n.T("cmd.dev.work.use_commit_flag")))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push repos with unpushed commits
|
// Push repos with unpushed commits
|
||||||
if len(aheadRepos) == 0 {
|
if len(aheadRepos) == 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println(i18n.T("cmd.dev.work.all_up_to_date"))
|
cli.Text(i18n.T("cmd.dev.work.all_up_to_date"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", i18n.T("common.count.repos_unpushed", map[string]interface{}{"Count": len(aheadRepos)}))
|
cli.Print("%s\n", i18n.T("common.count.repos_unpushed", map[string]interface{}{"Count": len(aheadRepos)}))
|
||||||
for _, s := range aheadRepos {
|
for _, s := range aheadRepos {
|
||||||
fmt.Printf(" %s: %s\n", s.Name, i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead}))
|
cli.Print(" %s: %s\n", s.Name, i18n.T("common.count.commits", map[string]interface{}{"Count": s.Ahead}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
if !cli.Confirm(i18n.T("cmd.dev.push.confirm")) {
|
if !cli.Confirm(i18n.T("cmd.dev.push.confirm")) {
|
||||||
fmt.Println(i18n.T("cli.aborted"))
|
cli.Text(i18n.T("cli.aborted"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// PERFORM push for each repo
|
// PERFORM push for each repo
|
||||||
var divergedRepos []git.RepoStatus
|
var divergedRepos []git.RepoStatus
|
||||||
|
|
@ -184,47 +182,47 @@ func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
})
|
})
|
||||||
if !handled {
|
if !handled {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), s.Name, "git service not available")
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, "git service not available")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsNonFastForward(err) {
|
if git.IsNonFastForward(err) {
|
||||||
fmt.Printf(" %s %s: %s\n", warningStyle.Render("!"), s.Name, i18n.T("cmd.dev.push.diverged"))
|
cli.Print(" %s %s: %s\n", warningStyle.Render("!"), s.Name, i18n.T("cmd.dev.push.diverged"))
|
||||||
divergedRepos = append(divergedRepos, s)
|
divergedRepos = append(divergedRepos, s)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), s.Name)
|
cli.Print(" %s %s\n", successStyle.Render("v"), s.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle diverged repos - offer to pull and retry
|
// Handle diverged repos - offer to pull and retry
|
||||||
if len(divergedRepos) > 0 {
|
if len(divergedRepos) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s\n", i18n.T("cmd.dev.push.diverged_help"))
|
cli.Print("%s\n", i18n.T("cmd.dev.push.diverged_help"))
|
||||||
if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) {
|
if cli.Confirm(i18n.T("cmd.dev.push.pull_and_retry")) {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
for _, s := range divergedRepos {
|
for _, s := range divergedRepos {
|
||||||
fmt.Printf(" %s %s...\n", dimStyle.Render("↓"), s.Name)
|
cli.Print(" %s %s...\n", dimStyle.Render("↓"), s.Name)
|
||||||
|
|
||||||
// PERFORM pull
|
// PERFORM pull
|
||||||
_, _, err := bundle.Core.PERFORM(git.TaskPull{Path: s.Path, Name: s.Name})
|
_, _, err := bundle.Core.PERFORM(git.TaskPull{Path: s.Path, Name: s.Name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s...\n", dimStyle.Render("↑"), s.Name)
|
cli.Print(" %s %s...\n", dimStyle.Render("↑"), s.Name)
|
||||||
|
|
||||||
// PERFORM push
|
// PERFORM push
|
||||||
_, _, err = bundle.Core.PERFORM(git.TaskPush{Path: s.Path, Name: s.Name})
|
_, _, err = bundle.Core.PERFORM(git.TaskPush{Path: s.Path, Name: s.Name})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("x"), s.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("v"), s.Name)
|
cli.Print(" %s %s\n", successStyle.Render("v"), s.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +240,7 @@ func printStatusTable(statuses []git.RepoStatus) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print header with fixed-width formatting
|
// Print header with fixed-width formatting
|
||||||
fmt.Printf("%-*s %8s %9s %6s %5s\n",
|
cli.Print("%-*s %8s %9s %6s %5s\n",
|
||||||
nameWidth,
|
nameWidth,
|
||||||
cli.TitleStyle.Render(i18n.Label("repo")),
|
cli.TitleStyle.Render(i18n.Label("repo")),
|
||||||
cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_modified")),
|
cli.TitleStyle.Render(i18n.T("cmd.dev.work.table_modified")),
|
||||||
|
|
@ -252,13 +250,13 @@ func printStatusTable(statuses []git.RepoStatus) {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Print separator
|
// Print separator
|
||||||
fmt.Println(strings.Repeat("-", nameWidth+2+10+11+8+7))
|
cli.Text(strings.Repeat("-", nameWidth+2+10+11+8+7))
|
||||||
|
|
||||||
// Print rows
|
// Print rows
|
||||||
for _, s := range statuses {
|
for _, s := range statuses {
|
||||||
if s.Error != nil {
|
if s.Error != nil {
|
||||||
paddedName := fmt.Sprintf("%-*s", nameWidth, s.Name)
|
paddedName := cli.Sprintf("%-*s", nameWidth, s.Name)
|
||||||
fmt.Printf("%s %s\n",
|
cli.Print("%s %s\n",
|
||||||
repoNameStyle.Render(paddedName),
|
repoNameStyle.Render(paddedName),
|
||||||
errorStyle.Render(i18n.T("cmd.dev.work.error_prefix")+" "+s.Error.Error()),
|
errorStyle.Render(i18n.T("cmd.dev.work.error_prefix")+" "+s.Error.Error()),
|
||||||
)
|
)
|
||||||
|
|
@ -266,28 +264,28 @@ func printStatusTable(statuses []git.RepoStatus) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style numbers based on values
|
// Style numbers based on values
|
||||||
modStr := fmt.Sprintf("%d", s.Modified)
|
modStr := cli.Sprintf("%d", s.Modified)
|
||||||
if s.Modified > 0 {
|
if s.Modified > 0 {
|
||||||
modStr = dirtyStyle.Render(modStr)
|
modStr = dirtyStyle.Render(modStr)
|
||||||
} else {
|
} else {
|
||||||
modStr = cleanStyle.Render(modStr)
|
modStr = cleanStyle.Render(modStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
untrackedStr := fmt.Sprintf("%d", s.Untracked)
|
untrackedStr := cli.Sprintf("%d", s.Untracked)
|
||||||
if s.Untracked > 0 {
|
if s.Untracked > 0 {
|
||||||
untrackedStr = dirtyStyle.Render(untrackedStr)
|
untrackedStr = dirtyStyle.Render(untrackedStr)
|
||||||
} else {
|
} else {
|
||||||
untrackedStr = cleanStyle.Render(untrackedStr)
|
untrackedStr = cleanStyle.Render(untrackedStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
stagedStr := fmt.Sprintf("%d", s.Staged)
|
stagedStr := cli.Sprintf("%d", s.Staged)
|
||||||
if s.Staged > 0 {
|
if s.Staged > 0 {
|
||||||
stagedStr = aheadStyle.Render(stagedStr)
|
stagedStr = aheadStyle.Render(stagedStr)
|
||||||
} else {
|
} else {
|
||||||
stagedStr = cleanStyle.Render(stagedStr)
|
stagedStr = cleanStyle.Render(stagedStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
aheadStr := fmt.Sprintf("%d", s.Ahead)
|
aheadStr := cli.Sprintf("%d", s.Ahead)
|
||||||
if s.Ahead > 0 {
|
if s.Ahead > 0 {
|
||||||
aheadStr = aheadStyle.Render(aheadStr)
|
aheadStr = aheadStyle.Render(aheadStr)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -295,8 +293,8 @@ func printStatusTable(statuses []git.RepoStatus) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pad name before styling to avoid ANSI code length issues
|
// Pad name before styling to avoid ANSI code length issues
|
||||||
paddedName := fmt.Sprintf("%-*s", nameWidth, s.Name)
|
paddedName := cli.Sprintf("%-*s", nameWidth, s.Name)
|
||||||
fmt.Printf("%s %8s %9s %6s %5s\n",
|
cli.Print("%s %8s %9s %6s %5s\n",
|
||||||
repoNameStyle.Render(paddedName),
|
repoNameStyle.Render(paddedName),
|
||||||
modStr,
|
modStr,
|
||||||
untrackedStr,
|
untrackedStr,
|
||||||
|
|
@ -339,25 +337,25 @@ func loadRegistry(registryPath string) ([]string, map[string]string, error) {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to load registry: %w", err)
|
return nil, nil, cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to load registry: %w", err)
|
return nil, nil, cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.Label("registry")), registryPath)
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory
|
// Fallback: scan current directory
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to scan directory: %w", err)
|
return nil, nil, cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.dev.scanning_label")), cwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/agentic"
|
"github.com/host-uk/core/pkg/agentic"
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/framework"
|
"github.com/host-uk/core/pkg/framework"
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
|
|
@ -73,7 +73,7 @@ func (s *Service) runWork(task TaskWork) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
fmt.Println("No git repositories found")
|
cli.Text("No git repositories found")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ func (s *Service) runWork(task TaskWork) error {
|
||||||
Names: names,
|
Names: names,
|
||||||
})
|
})
|
||||||
if !handled {
|
if !handled {
|
||||||
return fmt.Errorf("git service not available")
|
return cli.Err("git service not available")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -116,9 +116,9 @@ func (s *Service) runWork(task TaskWork) error {
|
||||||
|
|
||||||
// Auto-commit dirty repos if requested
|
// Auto-commit dirty repos if requested
|
||||||
if task.AutoCommit && len(dirtyRepos) > 0 {
|
if task.AutoCommit && len(dirtyRepos) > 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println("Committing changes...")
|
cli.Text("Committing changes...")
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
for _, repo := range dirtyRepos {
|
for _, repo := range dirtyRepos {
|
||||||
_, handled, err := s.Core().PERFORM(agentic.TaskCommit{
|
_, handled, err := s.Core().PERFORM(agentic.TaskCommit{
|
||||||
|
|
@ -127,13 +127,13 @@ func (s *Service) runWork(task TaskWork) error {
|
||||||
})
|
})
|
||||||
if !handled {
|
if !handled {
|
||||||
// Agentic service not available - skip silently
|
// Agentic service not available - skip silently
|
||||||
fmt.Printf(" - %s: agentic service not available\n", repo.Name)
|
cli.Print(" - %s: agentic service not available\n", repo.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" x %s: %s\n", repo.Name, err)
|
cli.Print(" x %s: %s\n", repo.Name, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" v %s\n", repo.Name)
|
cli.Print(" v %s\n", repo.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,35 +156,35 @@ func (s *Service) runWork(task TaskWork) error {
|
||||||
// If status only, we're done
|
// If status only, we're done
|
||||||
if task.StatusOnly {
|
if task.StatusOnly {
|
||||||
if len(dirtyRepos) > 0 && !task.AutoCommit {
|
if len(dirtyRepos) > 0 && !task.AutoCommit {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println("Use --commit flag to auto-commit dirty repos")
|
cli.Text("Use --commit flag to auto-commit dirty repos")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push repos with unpushed commits
|
// Push repos with unpushed commits
|
||||||
if len(aheadRepos) == 0 {
|
if len(aheadRepos) == 0 {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Println("All repositories are up to date")
|
cli.Text("All repositories are up to date")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%d repos with unpushed commits:\n", len(aheadRepos))
|
cli.Print("%d repos with unpushed commits:\n", len(aheadRepos))
|
||||||
for _, st := range aheadRepos {
|
for _, st := range aheadRepos {
|
||||||
fmt.Printf(" %s: %d commits\n", st.Name, st.Ahead)
|
cli.Print(" %s: %d commits\n", st.Name, st.Ahead)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Print("Push all? [y/N] ")
|
cli.Print("Push all? [y/N] ")
|
||||||
var answer string
|
var answer string
|
||||||
fmt.Scanln(&answer)
|
cli.Scanln(&answer)
|
||||||
if strings.ToLower(answer) != "y" {
|
if strings.ToLower(answer) != "y" {
|
||||||
fmt.Println("Aborted")
|
cli.Text("Aborted")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Push each repo
|
// Push each repo
|
||||||
for _, st := range aheadRepos {
|
for _, st := range aheadRepos {
|
||||||
|
|
@ -193,17 +193,17 @@ func (s *Service) runWork(task TaskWork) error {
|
||||||
Name: st.Name,
|
Name: st.Name,
|
||||||
})
|
})
|
||||||
if !handled {
|
if !handled {
|
||||||
fmt.Printf(" x %s: git service not available\n", st.Name)
|
cli.Print(" x %s: git service not available\n", st.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsNonFastForward(err) {
|
if git.IsNonFastForward(err) {
|
||||||
fmt.Printf(" ! %s: branch has diverged\n", st.Name)
|
cli.Print(" ! %s: branch has diverged\n", st.Name)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" x %s: %s\n", st.Name, err)
|
cli.Print(" x %s: %s\n", st.Name, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" v %s\n", st.Name)
|
cli.Print(" v %s\n", st.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +217,7 @@ func (s *Service) runStatus(task TaskStatus) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
fmt.Println("No git repositories found")
|
cli.Text("No git repositories found")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ func (s *Service) runStatus(task TaskStatus) error {
|
||||||
Names: names,
|
Names: names,
|
||||||
})
|
})
|
||||||
if !handled {
|
if !handled {
|
||||||
return fmt.Errorf("git service not available")
|
return cli.Err("git service not available")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -248,25 +248,25 @@ func (s *Service) loadRegistry(registryPath string) ([]string, map[string]string
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to load registry: %w", err)
|
return nil, nil, cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("Registry: %s\n\n", registryPath)
|
cli.Print("Registry: %s\n\n", registryPath)
|
||||||
} else {
|
} else {
|
||||||
registryPath, err = repos.FindRegistry()
|
registryPath, err = repos.FindRegistry()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to load registry: %w", err)
|
return nil, nil, cli.Wrap(err, "failed to load registry")
|
||||||
}
|
}
|
||||||
fmt.Printf("Registry: %s\n\n", registryPath)
|
cli.Print("Registry: %s\n\n", registryPath)
|
||||||
} else {
|
} else {
|
||||||
// Fallback: scan current directory
|
// Fallback: scan current directory
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to scan directory: %w", err)
|
return nil, nil, cli.Wrap(err, "failed to scan directory")
|
||||||
}
|
}
|
||||||
fmt.Printf("Scanning: %s\n\n", cwd)
|
cli.Print("Scanning: %s\n\n", cwd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,20 +293,20 @@ func (s *Service) printStatusTable(statuses []git.RepoStatus) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print header
|
// Print header
|
||||||
fmt.Printf("%-*s %8s %9s %6s %5s\n",
|
cli.Print("%-*s %8s %9s %6s %5s\n",
|
||||||
nameWidth, "Repo", "Modified", "Untracked", "Staged", "Ahead")
|
nameWidth, "Repo", "Modified", "Untracked", "Staged", "Ahead")
|
||||||
|
|
||||||
// Print separator
|
// Print separator
|
||||||
fmt.Println(strings.Repeat("-", nameWidth+2+10+11+8+7))
|
cli.Text(strings.Repeat("-", nameWidth+2+10+11+8+7))
|
||||||
|
|
||||||
// Print rows
|
// Print rows
|
||||||
for _, st := range statuses {
|
for _, st := range statuses {
|
||||||
if st.Error != nil {
|
if st.Error != nil {
|
||||||
fmt.Printf("%-*s error: %s\n", nameWidth, st.Name, st.Error)
|
cli.Print("%-*s error: %s\n", nameWidth, st.Name, st.Error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%-*s %8d %9d %6d %5d\n",
|
cli.Print("%-*s %8d %9d %6d %5d\n",
|
||||||
nameWidth, st.Name,
|
nameWidth, st.Name,
|
||||||
st.Modified, st.Untracked, st.Staged, st.Ahead)
|
st.Modified, st.Untracked, st.Staged, st.Ahead)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,13 @@
|
||||||
// to a central location for unified documentation builds.
|
// to a central location for unified documentation builds.
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import (
|
import "github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/cli"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cli.RegisterCommands(AddDocsCommands)
|
cli.RegisterCommands(AddDocsCommands)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDocsCommands registers the 'docs' command and all subcommands.
|
// AddDocsCommands registers the 'docs' command and all subcommands.
|
||||||
func AddDocsCommands(root *cobra.Command) {
|
func AddDocsCommands(root *cli.Command) {
|
||||||
root.AddCommand(docsCmd)
|
root.AddCommand(docsCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ package docs
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style and utility aliases from shared
|
// Style and utility aliases from shared
|
||||||
|
|
@ -20,7 +19,7 @@ var (
|
||||||
docsFileStyle = cli.InfoStyle
|
docsFileStyle = cli.InfoStyle
|
||||||
)
|
)
|
||||||
|
|
||||||
var docsCmd = &cobra.Command{
|
var docsCmd = &cli.Command{
|
||||||
Use: "docs",
|
Use: "docs",
|
||||||
Short: i18n.T("cmd.docs.short"),
|
Short: i18n.T("cmd.docs.short"),
|
||||||
Long: i18n.T("cmd.docs.long"),
|
Long: i18n.T("cmd.docs.long"),
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flag variable for list command
|
// Flag variable for list command
|
||||||
var docsListRegistryPath string
|
var docsListRegistryPath string
|
||||||
|
|
||||||
var docsListCmd = &cobra.Command{
|
var docsListCmd = &cli.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: i18n.T("cmd.docs.list.short"),
|
Short: i18n.T("cmd.docs.list.short"),
|
||||||
Long: i18n.T("cmd.docs.list.long"),
|
Long: i18n.T("cmd.docs.list.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runDocsList(docsListRegistryPath)
|
return runDocsList(docsListRegistryPath)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -31,14 +29,14 @@ func runDocsList(registryPath string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%-20s %-8s %-8s %-10s %s\n",
|
cli.Print("\n%-20s %-8s %-8s %-10s %s\n",
|
||||||
headerStyle.Render(i18n.Label("repo")),
|
headerStyle.Render(i18n.Label("repo")),
|
||||||
headerStyle.Render(i18n.T("cmd.docs.list.header.readme")),
|
headerStyle.Render(i18n.T("cmd.docs.list.header.readme")),
|
||||||
headerStyle.Render(i18n.T("cmd.docs.list.header.claude")),
|
headerStyle.Render(i18n.T("cmd.docs.list.header.claude")),
|
||||||
headerStyle.Render(i18n.T("cmd.docs.list.header.changelog")),
|
headerStyle.Render(i18n.T("cmd.docs.list.header.changelog")),
|
||||||
headerStyle.Render(i18n.T("cmd.docs.list.header.docs")),
|
headerStyle.Render(i18n.T("cmd.docs.list.header.docs")),
|
||||||
)
|
)
|
||||||
fmt.Println(strings.Repeat("─", 70))
|
cli.Text(strings.Repeat("─", 70))
|
||||||
|
|
||||||
var withDocs, withoutDocs int
|
var withDocs, withoutDocs int
|
||||||
for _, repo := range reg.List() {
|
for _, repo := range reg.List() {
|
||||||
|
|
@ -53,7 +51,7 @@ func runDocsList(registryPath string) error {
|
||||||
docsDir = docsFoundStyle.Render(i18n.T("common.count.files", map[string]interface{}{"Count": len(info.DocsFiles)}))
|
docsDir = docsFoundStyle.Render(i18n.T("common.count.files", map[string]interface{}{"Count": len(info.DocsFiles)}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%-20s %-8s %-8s %-10s %s\n",
|
cli.Print("%-20s %-8s %-8s %-10s %s\n",
|
||||||
repoNameStyle.Render(info.Name),
|
repoNameStyle.Render(info.Name),
|
||||||
readme,
|
readme,
|
||||||
claude,
|
claude,
|
||||||
|
|
@ -68,8 +66,8 @@ func runDocsList(registryPath string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf("%s %s\n",
|
cli.Print("%s %s\n",
|
||||||
cli.Label(i18n.Label("coverage")),
|
cli.Label(i18n.Label("coverage")),
|
||||||
i18n.T("cmd.docs.list.coverage_summary", map[string]interface{}{"WithDocs": withDocs, "WithoutDocs": withoutDocs}),
|
i18n.T("cmd.docs.list.coverage_summary", map[string]interface{}{"WithDocs": withDocs, "WithoutDocs": withoutDocs}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
)
|
)
|
||||||
|
|
@ -30,7 +30,7 @@ func loadRegistry(registryPath string) (*repos.Registry, string, error) {
|
||||||
if registryPath != "" {
|
if registryPath != "" {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "registry"), err)
|
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
|
||||||
}
|
}
|
||||||
basePath = filepath.Dir(registryPath)
|
basePath = filepath.Dir(registryPath)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -38,14 +38,14 @@ func loadRegistry(registryPath string) (*repos.Registry, string, error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
reg, err = repos.LoadRegistry(registryPath)
|
reg, err = repos.LoadRegistry(registryPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("%s: %w", i18n.T("i18n.fail.load", "registry"), err)
|
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.load", "registry"))
|
||||||
}
|
}
|
||||||
basePath = filepath.Dir(registryPath)
|
basePath = filepath.Dir(registryPath)
|
||||||
} else {
|
} else {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
reg, err = repos.ScanDirectory(cwd)
|
reg, err = repos.ScanDirectory(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("%s: %w", i18n.T("i18n.fail.scan", "directory"), err)
|
return nil, "", cli.Wrap(err, i18n.T("i18n.fail.scan", "directory"))
|
||||||
}
|
}
|
||||||
basePath = cwd
|
basePath = cwd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flag variables for sync command
|
// Flag variables for sync command
|
||||||
|
|
@ -17,11 +16,11 @@ var (
|
||||||
docsSyncOutputDir string
|
docsSyncOutputDir string
|
||||||
)
|
)
|
||||||
|
|
||||||
var docsSyncCmd = &cobra.Command{
|
var docsSyncCmd = &cli.Command{
|
||||||
Use: "sync",
|
Use: "sync",
|
||||||
Short: i18n.T("cmd.docs.sync.short"),
|
Short: i18n.T("cmd.docs.sync.short"),
|
||||||
Long: i18n.T("cmd.docs.sync.long"),
|
Long: i18n.T("cmd.docs.sync.long"),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun)
|
return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -83,45 +82,45 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(docsInfo) == 0 {
|
if len(docsInfo) == 0 {
|
||||||
fmt.Println(i18n.T("cmd.docs.sync.no_docs_found"))
|
cli.Text(i18n.T("cmd.docs.sync.no_docs_found"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n\n", dimStyle.Render(i18n.T("cmd.docs.sync.found_label")), i18n.T("cmd.docs.sync.repos_with_docs", map[string]interface{}{"Count": len(docsInfo)}))
|
cli.Print("\n%s %s\n\n", dimStyle.Render(i18n.T("cmd.docs.sync.found_label")), i18n.T("cmd.docs.sync.repos_with_docs", map[string]interface{}{"Count": len(docsInfo)}))
|
||||||
|
|
||||||
// Show what will be synced
|
// Show what will be synced
|
||||||
var totalFiles int
|
var totalFiles int
|
||||||
for _, info := range docsInfo {
|
for _, info := range docsInfo {
|
||||||
totalFiles += len(info.DocsFiles)
|
totalFiles += len(info.DocsFiles)
|
||||||
outName := packageOutputName(info.Name)
|
outName := packageOutputName(info.Name)
|
||||||
fmt.Printf(" %s → %s %s\n",
|
cli.Print(" %s → %s %s\n",
|
||||||
repoNameStyle.Render(info.Name),
|
repoNameStyle.Render(info.Name),
|
||||||
docsFileStyle.Render("packages/"+outName+"/"),
|
docsFileStyle.Render("packages/"+outName+"/"),
|
||||||
dimStyle.Render(i18n.T("cmd.docs.sync.files_count", map[string]interface{}{"Count": len(info.DocsFiles)})))
|
dimStyle.Render(i18n.T("cmd.docs.sync.files_count", map[string]interface{}{"Count": len(info.DocsFiles)})))
|
||||||
|
|
||||||
for _, f := range info.DocsFiles {
|
for _, f := range info.DocsFiles {
|
||||||
fmt.Printf(" %s\n", dimStyle.Render(f))
|
cli.Print(" %s\n", dimStyle.Render(f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n",
|
cli.Print("\n%s %s\n",
|
||||||
dimStyle.Render(i18n.Label("total")),
|
dimStyle.Render(i18n.Label("total")),
|
||||||
i18n.T("cmd.docs.sync.total_summary", map[string]interface{}{"Files": totalFiles, "Repos": len(docsInfo), "Output": outputDir}))
|
i18n.T("cmd.docs.sync.total_summary", map[string]interface{}{"Files": totalFiles, "Repos": len(docsInfo), "Output": outputDir}))
|
||||||
|
|
||||||
if dryRun {
|
if dryRun {
|
||||||
fmt.Printf("\n%s\n", dimStyle.Render(i18n.T("cmd.docs.sync.dry_run_notice")))
|
cli.Print("\n%s\n", dimStyle.Render(i18n.T("cmd.docs.sync.dry_run_notice")))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm
|
// Confirm
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
if !confirm(i18n.T("cmd.docs.sync.confirm")) {
|
if !confirm(i18n.T("cmd.docs.sync.confirm")) {
|
||||||
fmt.Println(i18n.T("common.prompt.abort"))
|
cli.Text(i18n.T("common.prompt.abort"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync docs
|
// Sync docs
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
var synced int
|
var synced int
|
||||||
for _, info := range docsInfo {
|
for _, info := range docsInfo {
|
||||||
outName := packageOutputName(info.Name)
|
outName := packageOutputName(info.Name)
|
||||||
|
|
@ -131,7 +130,7 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error {
|
||||||
os.RemoveAll(repoOutDir)
|
os.RemoveAll(repoOutDir)
|
||||||
|
|
||||||
if err := os.MkdirAll(repoOutDir, 0755); err != nil {
|
if err := os.MkdirAll(repoOutDir, 0755); err != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("✗"), info.Name, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("✗"), info.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,15 +141,15 @@ func runDocsSync(registryPath string, outputDir string, dryRun bool) error {
|
||||||
dst := filepath.Join(repoOutDir, f)
|
dst := filepath.Join(repoOutDir, f)
|
||||||
os.MkdirAll(filepath.Dir(dst), 0755)
|
os.MkdirAll(filepath.Dir(dst), 0755)
|
||||||
if err := copyFile(src, dst); err != nil {
|
if err := copyFile(src, dst); err != nil {
|
||||||
fmt.Printf(" %s %s: %s\n", errorStyle.Render("✗"), f, err)
|
cli.Print(" %s %s: %s\n", errorStyle.Render("✗"), f, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s → packages/%s/\n", successStyle.Render("✓"), info.Name, outName)
|
cli.Print(" %s %s → packages/%s/\n", successStyle.Render("✓"), info.Name, outName)
|
||||||
synced++
|
synced++
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.sync")), i18n.T("cmd.docs.sync.synced_packages", map[string]interface{}{"Count": synced}))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.sync")), i18n.T("cmd.docs.sync.synced_packages", map[string]interface{}{"Count": synced}))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -14,12 +14,12 @@ var (
|
||||||
fmtCheck bool
|
fmtCheck bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGoFmtCommand(parent *cobra.Command) {
|
func addGoFmtCommand(parent *cli.Command) {
|
||||||
fmtCmd := &cobra.Command{
|
fmtCmd := &cli.Command{
|
||||||
Use: "fmt",
|
Use: "fmt",
|
||||||
Short: "Format Go code",
|
Short: "Format Go code",
|
||||||
Long: "Format Go code using goimports or gofmt",
|
Long: "Format Go code using goimports or gofmt",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
fmtArgs := []string{}
|
fmtArgs := []string{}
|
||||||
if fmtFix {
|
if fmtFix {
|
||||||
fmtArgs = append(fmtArgs, "-w")
|
fmtArgs = append(fmtArgs, "-w")
|
||||||
|
|
@ -55,12 +55,12 @@ func addGoFmtCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
var lintFix bool
|
var lintFix bool
|
||||||
|
|
||||||
func addGoLintCommand(parent *cobra.Command) {
|
func addGoLintCommand(parent *cli.Command) {
|
||||||
lintCmd := &cobra.Command{
|
lintCmd := &cli.Command{
|
||||||
Use: "lint",
|
Use: "lint",
|
||||||
Short: "Run golangci-lint",
|
Short: "Run golangci-lint",
|
||||||
Long: "Run golangci-lint for comprehensive static analysis",
|
Long: "Run golangci-lint for comprehensive static analysis",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
lintArgs := []string{"run"}
|
lintArgs := []string{"run"}
|
||||||
if lintFix {
|
if lintFix {
|
||||||
lintArgs = append(lintArgs, "--fix")
|
lintArgs = append(lintArgs, "--fix")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ package gocmd
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases for shared styles
|
// Style aliases for shared styles
|
||||||
|
|
@ -17,8 +16,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddGoCommands adds Go development commands.
|
// AddGoCommands adds Go development commands.
|
||||||
func AddGoCommands(root *cobra.Command) {
|
func AddGoCommands(root *cli.Command) {
|
||||||
goCmd := &cobra.Command{
|
goCmd := &cli.Command{
|
||||||
Use: "go",
|
Use: "go",
|
||||||
Short: i18n.T("cmd.go.short"),
|
Short: i18n.T("cmd.go.short"),
|
||||||
Long: i18n.T("cmd.go.long"),
|
Long: i18n.T("cmd.go.long"),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import (
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -24,12 +23,12 @@ var (
|
||||||
testVerbose bool
|
testVerbose bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGoTestCommand(parent *cobra.Command) {
|
func addGoTestCommand(parent *cli.Command) {
|
||||||
testCmd := &cobra.Command{
|
testCmd := &cli.Command{
|
||||||
Use: "test",
|
Use: "test",
|
||||||
Short: "Run Go tests",
|
Short: "Run Go tests",
|
||||||
Long: "Run Go tests with optional coverage, filtering, and race detection",
|
Long: "Run Go tests with optional coverage, filtering, and race detection",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
return runGoTest(testCoverage, testPkg, testRun, testShort, testRace, testJSON, testVerbose)
|
return runGoTest(testCoverage, testPkg, testRun, testShort, testRace, testJSON, testVerbose)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -74,9 +73,9 @@ func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose boo
|
||||||
args = append(args, pkg)
|
args = append(args, pkg)
|
||||||
|
|
||||||
if !jsonOut {
|
if !jsonOut {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("test")), i18n.ProgressSubject("run", "tests"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("test")), i18n.ProgressSubject("run", "tests"))
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("package")), pkg)
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("package")), pkg)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("go", args...)
|
cmd := exec.Command("go", args...)
|
||||||
|
|
@ -101,34 +100,34 @@ func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose boo
|
||||||
cov := parseOverallCoverage(outputStr)
|
cov := parseOverallCoverage(outputStr)
|
||||||
|
|
||||||
if jsonOut {
|
if jsonOut {
|
||||||
fmt.Printf(`{"passed":%d,"failed":%d,"skipped":%d,"coverage":%.1f,"exit_code":%d}`,
|
cli.Print(`{"passed":%d,"failed":%d,"skipped":%d,"coverage":%.1f,"exit_code":%d}`,
|
||||||
passed, failed, skipped, cov, cmd.ProcessState.ExitCode())
|
passed, failed, skipped, cov, cmd.ProcessState.ExitCode())
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print filtered output if verbose or failed
|
// Print filtered output if verbose or failed
|
||||||
if verbose || err != nil {
|
if verbose || err != nil {
|
||||||
fmt.Println(outputStr)
|
cli.Text(outputStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render(cli.SymbolCheck), i18n.T("i18n.count.test", passed)+" "+i18n.T("i18n.done.pass"))
|
cli.Print(" %s %s\n", successStyle.Render(cli.SymbolCheck), i18n.T("i18n.count.test", passed)+" "+i18n.T("i18n.done.pass"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s %s, %s\n", errorStyle.Render(cli.SymbolCross),
|
cli.Print(" %s %s, %s\n", errorStyle.Render(cli.SymbolCross),
|
||||||
i18n.T("i18n.count.test", passed)+" "+i18n.T("i18n.done.pass"),
|
i18n.T("i18n.count.test", passed)+" "+i18n.T("i18n.done.pass"),
|
||||||
i18n.T("i18n.count.test", failed)+" "+i18n.T("i18n.done.fail"))
|
i18n.T("i18n.count.test", failed)+" "+i18n.T("i18n.done.fail"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cov > 0 {
|
if cov > 0 {
|
||||||
fmt.Printf("\n %s %s\n", cli.ProgressLabel(i18n.Label("coverage")), cli.FormatCoverage(cov))
|
cli.Print("\n %s %s\n", cli.ProgressLabel(i18n.Label("coverage")), cli.FormatCoverage(cov))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("\n%s\n", successStyle.Render(i18n.T("i18n.done.pass")))
|
cli.Print("\n%s\n", successStyle.Render(i18n.T("i18n.done.pass")))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s\n", errorStyle.Render(i18n.T("i18n.done.fail")))
|
cli.Print("\n%s\n", errorStyle.Render(i18n.T("i18n.done.fail")))
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
@ -168,18 +167,18 @@ var (
|
||||||
covThreshold float64
|
covThreshold float64
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGoCovCommand(parent *cobra.Command) {
|
func addGoCovCommand(parent *cli.Command) {
|
||||||
covCmd := &cobra.Command{
|
covCmd := &cli.Command{
|
||||||
Use: "cov",
|
Use: "cov",
|
||||||
Short: "Run tests with coverage report",
|
Short: "Run tests with coverage report",
|
||||||
Long: "Run tests with detailed coverage reports, HTML output, and threshold checking",
|
Long: "Run tests with detailed coverage reports, HTML output, and threshold checking",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
pkg := covPkg
|
pkg := covPkg
|
||||||
if pkg == "" {
|
if pkg == "" {
|
||||||
// Auto-discover packages with tests
|
// Auto-discover packages with tests
|
||||||
pkgs, err := findTestPackages(".")
|
pkgs, err := findTestPackages(".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.find", "test packages"), err)
|
return cli.Wrap(err, i18n.T("i18n.fail.find", "test packages"))
|
||||||
}
|
}
|
||||||
if len(pkgs) == 0 {
|
if len(pkgs) == 0 {
|
||||||
return errors.New("no test packages found")
|
return errors.New("no test packages found")
|
||||||
|
|
@ -190,20 +189,20 @@ func addGoCovCommand(parent *cobra.Command) {
|
||||||
// Create temp file for coverage data
|
// Create temp file for coverage data
|
||||||
covFile, err := os.CreateTemp("", "coverage-*.out")
|
covFile, err := os.CreateTemp("", "coverage-*.out")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.create", "coverage file"), err)
|
return cli.Wrap(err, i18n.T("i18n.fail.create", "coverage file"))
|
||||||
}
|
}
|
||||||
covPath := covFile.Name()
|
covPath := covFile.Name()
|
||||||
covFile.Close()
|
covFile.Close()
|
||||||
defer os.Remove(covPath)
|
defer os.Remove(covPath)
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("coverage")), i18n.ProgressSubject("run", "tests"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("coverage")), i18n.ProgressSubject("run", "tests"))
|
||||||
// Truncate package list if too long for display
|
// Truncate package list if too long for display
|
||||||
displayPkg := pkg
|
displayPkg := pkg
|
||||||
if len(displayPkg) > 60 {
|
if len(displayPkg) > 60 {
|
||||||
displayPkg = displayPkg[:57] + "..."
|
displayPkg = displayPkg[:57] + "..."
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("package")), displayPkg)
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("package")), displayPkg)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Run tests with coverage
|
// Run tests with coverage
|
||||||
// We need to split pkg into individual arguments if it contains spaces
|
// We need to split pkg into individual arguments if it contains spaces
|
||||||
|
|
@ -224,7 +223,7 @@ func addGoCovCommand(parent *cobra.Command) {
|
||||||
if testErr != nil {
|
if testErr != nil {
|
||||||
return testErr
|
return testErr
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "coverage"), err)
|
return cli.Wrap(err, i18n.T("i18n.fail.get", "coverage"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse total coverage from last line
|
// Parse total coverage from last line
|
||||||
|
|
@ -243,17 +242,17 @@ func addGoCovCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print coverage summary
|
// Print coverage summary
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
fmt.Printf(" %s %s\n", cli.ProgressLabel(i18n.Label("total")), cli.FormatCoverage(totalCov))
|
cli.Print(" %s %s\n", cli.ProgressLabel(i18n.Label("total")), cli.FormatCoverage(totalCov))
|
||||||
|
|
||||||
// Generate HTML if requested
|
// Generate HTML if requested
|
||||||
if covHTML || covOpen {
|
if covHTML || covOpen {
|
||||||
htmlPath := "coverage.html"
|
htmlPath := "coverage.html"
|
||||||
htmlCmd := exec.Command("go", "tool", "cover", "-html="+covPath, "-o="+htmlPath)
|
htmlCmd := exec.Command("go", "tool", "cover", "-html="+covPath, "-o="+htmlPath)
|
||||||
if err := htmlCmd.Run(); err != nil {
|
if err := htmlCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.generate", "HTML"), err)
|
return cli.Wrap(err, i18n.T("i18n.fail.generate", "HTML"))
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("html")), htmlPath)
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("html")), htmlPath)
|
||||||
|
|
||||||
if covOpen {
|
if covOpen {
|
||||||
// Open in browser
|
// Open in browser
|
||||||
|
|
@ -264,7 +263,7 @@ func addGoCovCommand(parent *cobra.Command) {
|
||||||
case exec.Command("which", "xdg-open").Run() == nil:
|
case exec.Command("which", "xdg-open").Run() == nil:
|
||||||
openCmd = exec.Command("xdg-open", htmlPath)
|
openCmd = exec.Command("xdg-open", htmlPath)
|
||||||
default:
|
default:
|
||||||
fmt.Printf(" %s\n", dimStyle.Render("Open coverage.html in your browser"))
|
cli.Print(" %s\n", dimStyle.Render("Open coverage.html in your browser"))
|
||||||
}
|
}
|
||||||
if openCmd != nil {
|
if openCmd != nil {
|
||||||
openCmd.Run()
|
openCmd.Run()
|
||||||
|
|
@ -274,7 +273,7 @@ func addGoCovCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
// Check threshold
|
// Check threshold
|
||||||
if covThreshold > 0 && totalCov < covThreshold {
|
if covThreshold > 0 && totalCov < covThreshold {
|
||||||
fmt.Printf("\n%s %.1f%% < %.1f%%\n", errorStyle.Render(i18n.T("i18n.fail.meet", "threshold")), totalCov, covThreshold)
|
cli.Print("\n%s %.1f%% < %.1f%%\n", errorStyle.Render(i18n.T("i18n.fail.meet", "threshold")), totalCov, covThreshold)
|
||||||
return errors.New("coverage below threshold")
|
return errors.New("coverage below threshold")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,7 +281,7 @@ func addGoCovCommand(parent *cobra.Command) {
|
||||||
return testErr
|
return testErr
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s\n", successStyle.Render(i18n.T("i18n.done.pass")))
|
cli.Print("\n%s\n", successStyle.Render(i18n.T("i18n.done.pass")))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,18 @@ package gocmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/cli"
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var qaFix bool
|
var qaFix bool
|
||||||
|
|
||||||
func addGoQACommand(parent *cobra.Command) {
|
func addGoQACommand(parent *cli.Command) {
|
||||||
qaCmd := &cobra.Command{
|
qaCmd := &cli.Command{
|
||||||
Use: "qa",
|
Use: "qa",
|
||||||
Short: "Run QA checks",
|
Short: "Run QA checks",
|
||||||
Long: "Run code quality checks: formatting, vetting, linting, and testing",
|
Long: "Run code quality checks: formatting, vetting, linting, and testing",
|
||||||
|
|
@ -25,65 +23,67 @@ func addGoQACommand(parent *cobra.Command) {
|
||||||
qaCmd.PersistentFlags().BoolVar(&qaFix, "fix", false, i18n.T("common.flag.fix"))
|
qaCmd.PersistentFlags().BoolVar(&qaFix, "fix", false, i18n.T("common.flag.fix"))
|
||||||
|
|
||||||
// Subcommands for individual checks
|
// Subcommands for individual checks
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "fmt",
|
Use: "fmt",
|
||||||
Short: "Check/fix code formatting",
|
Short: "Check/fix code formatting",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"fmt"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"fmt"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "vet",
|
Use: "vet",
|
||||||
Short: "Run go vet",
|
Short: "Run go vet",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"vet"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"vet"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "lint",
|
Use: "lint",
|
||||||
Short: "Run golangci-lint",
|
Short: "Run golangci-lint",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"lint"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"lint"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "test",
|
Use: "test",
|
||||||
Short: "Run tests",
|
Short: "Run tests",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"test"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"test"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "race",
|
Use: "race",
|
||||||
Short: "Run tests with race detector",
|
Short: "Run tests with race detector",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"race"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"race"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "vuln",
|
Use: "vuln",
|
||||||
Short: "Check for vulnerabilities",
|
Short: "Check for vulnerabilities",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"vuln"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"vuln"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "sec",
|
Use: "sec",
|
||||||
Short: "Run security scanner",
|
Short: "Run security scanner",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"sec"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"sec"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "quick",
|
Use: "quick",
|
||||||
Short: "Quick QA: fmt, vet, lint",
|
Short: "Quick QA: fmt, vet, lint",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"fmt", "vet", "lint"}) },
|
RunE: func(cmd *cli.Command, args []string) error { return runQAChecks([]string{"fmt", "vet", "lint"}) },
|
||||||
})
|
})
|
||||||
|
|
||||||
qaCmd.AddCommand(&cobra.Command{
|
qaCmd.AddCommand(&cli.Command{
|
||||||
Use: "full",
|
Use: "full",
|
||||||
Short: "Full QA: all checks including race, vuln, sec",
|
Short: "Full QA: all checks including race, vuln, sec",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error { return runQAChecks([]string{"fmt", "vet", "lint", "test", "race", "vuln", "sec"}) },
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
|
return runQAChecks([]string{"fmt", "vet", "lint", "test", "race", "vuln", "sec"})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
parent.AddCommand(qaCmd)
|
parent.AddCommand(qaCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runGoQADefault runs the default QA checks (fmt, vet, lint, test)
|
// runGoQADefault runs the default QA checks (fmt, vet, lint, test)
|
||||||
func runGoQADefault(cmd *cobra.Command, args []string) error {
|
func runGoQADefault(cmd *cli.Command, args []string) error {
|
||||||
return runQAChecks([]string{"fmt", "vet", "lint", "test"})
|
return runQAChecks([]string{"fmt", "vet", "lint", "test"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,15 +97,15 @@ type QACheck struct {
|
||||||
func runQAChecks(checkNames []string) error {
|
func runQAChecks(checkNames []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Wrap(err, i18n.T("i18n.fail.get", "working directory"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect if this is a Go project
|
// Detect if this is a Go project
|
||||||
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
|
if _, err := os.Stat("go.mod"); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("not a Go project (no %s found)", i18n.T("gram.word.go_mod"))
|
return cli.Err("not a Go project (no %s found)", i18n.T("gram.word.go_mod"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", cli.DimStyle.Render(i18n.Label("qa")), i18n.ProgressSubject("run", "Go QA"))
|
cli.Print("%s %s\n\n", cli.DimStyle.Render(i18n.Label("qa")), i18n.ProgressSubject("run", "Go QA"))
|
||||||
|
|
||||||
checks := buildChecksForNames(checkNames)
|
checks := buildChecksForNames(checkNames)
|
||||||
|
|
||||||
|
|
@ -115,23 +115,23 @@ func runQAChecks(checkNames []string) error {
|
||||||
failed := 0
|
failed := 0
|
||||||
|
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
fmt.Printf("%s %s\n", cli.DimStyle.Render("→"), i18n.Progress(check.Name))
|
cli.Print("%s %s\n", cli.DimStyle.Render("→"), i18n.Progress(check.Name))
|
||||||
|
|
||||||
if err := runCheck(ctx, cwd, check); err != nil {
|
if err := runCheck(ctx, cwd, check); err != nil {
|
||||||
fmt.Printf(" %s %s\n", cli.ErrorStyle.Render(cli.SymbolCross), err.Error())
|
cli.Print(" %s %s\n", cli.ErrorStyle.Render(cli.SymbolCross), err.Error())
|
||||||
failed++
|
failed++
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" %s %s\n", cli.SuccessStyle.Render(cli.SymbolCheck), i18n.T("i18n.done.pass"))
|
cli.Print(" %s %s\n", cli.SuccessStyle.Render(cli.SymbolCheck), i18n.T("i18n.done.pass"))
|
||||||
passed++
|
passed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
duration := time.Since(startTime).Round(time.Millisecond)
|
duration := time.Since(startTime).Round(time.Millisecond)
|
||||||
|
|
||||||
if failed > 0 {
|
if failed > 0 {
|
||||||
fmt.Printf("%s %s, %s (%s)\n",
|
cli.Print("%s %s, %s (%s)\n",
|
||||||
cli.ErrorStyle.Render(cli.SymbolCross),
|
cli.ErrorStyle.Render(cli.SymbolCross),
|
||||||
i18n.T("i18n.count.check", passed)+" "+i18n.T("i18n.done.pass"),
|
i18n.T("i18n.count.check", passed)+" "+i18n.T("i18n.done.pass"),
|
||||||
i18n.T("i18n.count.check", failed)+" "+i18n.T("i18n.done.fail"),
|
i18n.T("i18n.count.check", failed)+" "+i18n.T("i18n.done.fail"),
|
||||||
|
|
@ -139,7 +139,7 @@ func runQAChecks(checkNames []string) error {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s (%s)\n",
|
cli.Print("%s %s (%s)\n",
|
||||||
cli.SuccessStyle.Render(cli.SymbolCheck),
|
cli.SuccessStyle.Render(cli.SymbolCheck),
|
||||||
i18n.T("i18n.count.check", passed)+" "+i18n.T("i18n.done.pass"),
|
i18n.T("i18n.count.check", passed)+" "+i18n.T("i18n.done.pass"),
|
||||||
duration)
|
duration)
|
||||||
|
|
@ -214,7 +214,7 @@ func lintArgs(fix bool) []string {
|
||||||
func runCheck(ctx context.Context, dir string, check QACheck) error {
|
func runCheck(ctx context.Context, dir string, check QACheck) error {
|
||||||
// Check if command exists
|
// Check if command exists
|
||||||
if _, err := exec.LookPath(check.Command); err != nil {
|
if _, err := exec.LookPath(check.Command); err != nil {
|
||||||
return fmt.Errorf("%s: %s", check.Command, i18n.T("i18n.done.miss"))
|
return cli.Err("%s: %s", check.Command, i18n.T("i18n.done.miss"))
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, check.Command, check.Args...)
|
cmd := exec.CommandContext(ctx, check.Command, check.Args...)
|
||||||
|
|
@ -228,8 +228,8 @@ func runCheck(ctx context.Context, dir string, check QACheck) error {
|
||||||
}
|
}
|
||||||
if len(output) > 0 {
|
if len(output) > 0 {
|
||||||
// Show files that need formatting
|
// Show files that need formatting
|
||||||
fmt.Print(string(output))
|
cli.Print(string(output))
|
||||||
return fmt.Errorf("%s (use --fix)", i18n.T("i18n.fail.format", i18n.T("i18n.count.file", len(output))))
|
return cli.Err("%s (use --fix)", i18n.T("i18n.fail.format", i18n.T("i18n.count.file", len(output))))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ package gocmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -16,12 +15,12 @@ var (
|
||||||
installNoCgo bool
|
installNoCgo bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGoInstallCommand(parent *cobra.Command) {
|
func addGoInstallCommand(parent *cli.Command) {
|
||||||
installCmd := &cobra.Command{
|
installCmd := &cli.Command{
|
||||||
Use: "install [path]",
|
Use: "install [path]",
|
||||||
Short: "Install Go binary",
|
Short: "Install Go binary",
|
||||||
Long: "Install Go binary to $GOPATH/bin",
|
Long: "Install Go binary to $GOPATH/bin",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
// Get install path from args or default to current dir
|
// Get install path from args or default to current dir
|
||||||
installPath := "./..."
|
installPath := "./..."
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
|
@ -39,10 +38,10 @@ func addGoInstallCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.Progress("install"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.Progress("install"))
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("path")), installPath)
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("path")), installPath)
|
||||||
if installNoCgo {
|
if installNoCgo {
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("cgo")), "disabled")
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("cgo")), "disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs := []string{"install"}
|
cmdArgs := []string{"install"}
|
||||||
|
|
@ -59,7 +58,7 @@ func addGoInstallCommand(parent *cobra.Command) {
|
||||||
execCmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
||||||
if err := execCmd.Run(); err != nil {
|
if err := execCmd.Run(); err != nil {
|
||||||
fmt.Printf("\n%s\n", errorStyle.Render(i18n.T("i18n.fail.install", "binary")))
|
cli.Print("\n%s\n", errorStyle.Render(i18n.T("i18n.fail.install", "binary")))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,7 +70,7 @@ func addGoInstallCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
binDir := filepath.Join(gopath, "bin")
|
binDir := filepath.Join(gopath, "bin")
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.install")), binDir)
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.T("i18n.done.install")), binDir)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -82,18 +81,18 @@ func addGoInstallCommand(parent *cobra.Command) {
|
||||||
parent.AddCommand(installCmd)
|
parent.AddCommand(installCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGoModCommand(parent *cobra.Command) {
|
func addGoModCommand(parent *cli.Command) {
|
||||||
modCmd := &cobra.Command{
|
modCmd := &cli.Command{
|
||||||
Use: "mod",
|
Use: "mod",
|
||||||
Short: "Module management",
|
Short: "Module management",
|
||||||
Long: "Go module management commands",
|
Long: "Go module management commands",
|
||||||
}
|
}
|
||||||
|
|
||||||
// tidy
|
// tidy
|
||||||
tidyCmd := &cobra.Command{
|
tidyCmd := &cli.Command{
|
||||||
Use: "tidy",
|
Use: "tidy",
|
||||||
Short: "Run go mod tidy",
|
Short: "Run go mod tidy",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
execCmd := exec.Command("go", "mod", "tidy")
|
execCmd := exec.Command("go", "mod", "tidy")
|
||||||
execCmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
execCmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
@ -102,10 +101,10 @@ func addGoModCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// download
|
// download
|
||||||
downloadCmd := &cobra.Command{
|
downloadCmd := &cli.Command{
|
||||||
Use: "download",
|
Use: "download",
|
||||||
Short: "Download module dependencies",
|
Short: "Download module dependencies",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
execCmd := exec.Command("go", "mod", "download")
|
execCmd := exec.Command("go", "mod", "download")
|
||||||
execCmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
execCmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
@ -114,10 +113,10 @@ func addGoModCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
verifyCmd := &cobra.Command{
|
verifyCmd := &cli.Command{
|
||||||
Use: "verify",
|
Use: "verify",
|
||||||
Short: "Verify module checksums",
|
Short: "Verify module checksums",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
execCmd := exec.Command("go", "mod", "verify")
|
execCmd := exec.Command("go", "mod", "verify")
|
||||||
execCmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
execCmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
@ -126,10 +125,10 @@ func addGoModCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// graph
|
// graph
|
||||||
graphCmd := &cobra.Command{
|
graphCmd := &cli.Command{
|
||||||
Use: "graph",
|
Use: "graph",
|
||||||
Short: "Print module dependency graph",
|
Short: "Print module dependency graph",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
execCmd := exec.Command("go", "mod", "graph")
|
execCmd := exec.Command("go", "mod", "graph")
|
||||||
execCmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
execCmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
@ -144,18 +143,18 @@ func addGoModCommand(parent *cobra.Command) {
|
||||||
parent.AddCommand(modCmd)
|
parent.AddCommand(modCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGoWorkCommand(parent *cobra.Command) {
|
func addGoWorkCommand(parent *cli.Command) {
|
||||||
workCmd := &cobra.Command{
|
workCmd := &cli.Command{
|
||||||
Use: "work",
|
Use: "work",
|
||||||
Short: "Workspace management",
|
Short: "Workspace management",
|
||||||
Long: "Go workspace management commands",
|
Long: "Go workspace management commands",
|
||||||
}
|
}
|
||||||
|
|
||||||
// sync
|
// sync
|
||||||
syncCmd := &cobra.Command{
|
syncCmd := &cli.Command{
|
||||||
Use: "sync",
|
Use: "sync",
|
||||||
Short: "Sync workspace modules",
|
Short: "Sync workspace modules",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
execCmd := exec.Command("go", "work", "sync")
|
execCmd := exec.Command("go", "work", "sync")
|
||||||
execCmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
execCmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
@ -164,10 +163,10 @@ func addGoWorkCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
initCmd := &cobra.Command{
|
initCmd := &cli.Command{
|
||||||
Use: "init",
|
Use: "init",
|
||||||
Short: "Initialise a new workspace",
|
Short: "Initialise a new workspace",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
execCmd := exec.Command("go", "work", "init")
|
execCmd := exec.Command("go", "work", "init")
|
||||||
execCmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
execCmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
@ -186,10 +185,10 @@ func addGoWorkCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// use
|
// use
|
||||||
useCmd := &cobra.Command{
|
useCmd := &cli.Command{
|
||||||
Use: "use [modules...]",
|
Use: "use [modules...]",
|
||||||
Short: "Add modules to workspace",
|
Short: "Add modules to workspace",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cli.Command, args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
// Auto-detect modules
|
// Auto-detect modules
|
||||||
modules := findGoModules(".")
|
modules := findGoModules(".")
|
||||||
|
|
@ -203,7 +202,7 @@ func addGoWorkCommand(parent *cobra.Command) {
|
||||||
if err := execCmd.Run(); err != nil {
|
if err := execCmd.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(i18n.T("i18n.done.add")), mod)
|
cli.Print("%s %s\n", successStyle.Render(i18n.T("i18n.done.add")), mod)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ package php
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -31,7 +31,7 @@ func addPHPBuildCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -87,22 +87,22 @@ func runPHPBuildDocker(ctx context.Context, projectDir string, opts dockerBuildO
|
||||||
return errors.New(i18n.T("cmd.php.error.not_php"))
|
return errors.New(i18n.T("cmd.php.error.not_php"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.build.building_docker"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.build.building_docker"))
|
||||||
|
|
||||||
// Show detected configuration
|
// Show detected configuration
|
||||||
config, err := DetectDockerfileConfig(projectDir)
|
config, err := DetectDockerfileConfig(projectDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.detect", "project configuration"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.detect", "project configuration"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.php_version")), config.PHPVersion)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.php_version")), config.PHPVersion)
|
||||||
fmt.Printf("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.laravel")), config.IsLaravel)
|
cli.Print("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.laravel")), config.IsLaravel)
|
||||||
fmt.Printf("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.octane")), config.HasOctane)
|
cli.Print("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.octane")), config.HasOctane)
|
||||||
fmt.Printf("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.frontend")), config.HasAssets)
|
cli.Print("%s %v\n", dimStyle.Render(i18n.T("cmd.php.build.frontend")), config.HasAssets)
|
||||||
if len(config.PHPExtensions) > 0 {
|
if len(config.PHPExtensions) > 0 {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.extensions")), strings.Join(config.PHPExtensions, ", "))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.extensions")), strings.Join(config.PHPExtensions, ", "))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Build options
|
// Build options
|
||||||
buildOpts := DockerBuildOptions{
|
buildOpts := DockerBuildOptions{
|
||||||
|
|
@ -128,18 +128,18 @@ func runPHPBuildDocker(ctx context.Context, projectDir string, opts dockerBuildO
|
||||||
buildOpts.Tag = "latest"
|
buildOpts.Tag = "latest"
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s:%s\n", dimStyle.Render(i18n.Label("image")), buildOpts.ImageName, buildOpts.Tag)
|
cli.Print("%s %s:%s\n", dimStyle.Render(i18n.Label("image")), buildOpts.ImageName, buildOpts.Tag)
|
||||||
if opts.Platform != "" {
|
if opts.Platform != "" {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.platform")), opts.Platform)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.platform")), opts.Platform)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if err := BuildDocker(ctx, buildOpts); err != nil {
|
if err := BuildDocker(ctx, buildOpts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.build"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.build"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Docker image built"}))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Docker image built"}))
|
||||||
fmt.Printf("%s docker run -p 80:80 -p 443:443 %s:%s\n",
|
cli.Print("%s docker run -p 80:80 -p 443:443 %s:%s\n",
|
||||||
dimStyle.Render(i18n.T("cmd.php.build.docker_run_with")),
|
dimStyle.Render(i18n.T("cmd.php.build.docker_run_with")),
|
||||||
buildOpts.ImageName, buildOpts.Tag)
|
buildOpts.ImageName, buildOpts.Tag)
|
||||||
|
|
||||||
|
|
@ -151,7 +151,7 @@ func runPHPBuildLinuxKit(ctx context.Context, projectDir string, opts linuxKitBu
|
||||||
return errors.New(i18n.T("cmd.php.error.not_php"))
|
return errors.New(i18n.T("cmd.php.error.not_php"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.build.building_linuxkit"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.build.building_linuxkit"))
|
||||||
|
|
||||||
buildOpts := LinuxKitBuildOptions{
|
buildOpts := LinuxKitBuildOptions{
|
||||||
ProjectDir: projectDir,
|
ProjectDir: projectDir,
|
||||||
|
|
@ -168,15 +168,15 @@ func runPHPBuildLinuxKit(ctx context.Context, projectDir string, opts linuxKitBu
|
||||||
buildOpts.Template = "server-php"
|
buildOpts.Template = "server-php"
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("template")), buildOpts.Template)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("template")), buildOpts.Template)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.format")), buildOpts.Format)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.build.format")), buildOpts.Format)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if err := BuildLinuxKit(ctx, buildOpts); err != nil {
|
if err := BuildLinuxKit(ctx, buildOpts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.build"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.build"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "LinuxKit image built"}))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "LinuxKit image built"}))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,8 +224,8 @@ func addPHPServeCommand(parent *cobra.Command) {
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.ProgressSubject("run", "production container"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.ProgressSubject("run", "production container"))
|
||||||
fmt.Printf("%s %s:%s\n", dimStyle.Render(i18n.Label("image")), imageName, func() string {
|
cli.Print("%s %s:%s\n", dimStyle.Render(i18n.Label("image")), imageName, func() string {
|
||||||
if serveTag == "" {
|
if serveTag == "" {
|
||||||
return "latest"
|
return "latest"
|
||||||
}
|
}
|
||||||
|
|
@ -241,16 +241,16 @@ func addPHPServeCommand(parent *cobra.Command) {
|
||||||
effectiveHTTPSPort = 443
|
effectiveHTTPSPort = 443
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s http://localhost:%d, https://localhost:%d\n",
|
cli.Print("%s http://localhost:%d, https://localhost:%d\n",
|
||||||
dimStyle.Render("Ports:"), effectivePort, effectiveHTTPSPort)
|
dimStyle.Render("Ports:"), effectivePort, effectiveHTTPSPort)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if err := ServeProduction(ctx, opts); err != nil {
|
if err := ServeProduction(ctx, opts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.start", "container"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.start", "container"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !serveDetach {
|
if !serveDetach {
|
||||||
fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.serve.stopped"))
|
cli.Print("\n%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.serve.stopped"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -277,10 +277,10 @@ func addPHPShellCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.shell.opening", map[string]interface{}{"Container": args[0]}))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.shell.opening", map[string]interface{}{"Container": args[0]}))
|
||||||
|
|
||||||
if err := Shell(ctx, args[0]); err != nil {
|
if err := Shell(ctx, args[0]); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.open", "shell"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.open", "shell"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -46,7 +45,7 @@ func addPHPDeployCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := EnvProduction
|
env := EnvProduction
|
||||||
|
|
@ -54,7 +53,7 @@ func addPHPDeployCommand(parent *cobra.Command) {
|
||||||
env = EnvStaging
|
env = EnvStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy.deploying", map[string]interface{}{"Environment": env}))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy.deploying", map[string]interface{}{"Environment": env}))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -67,19 +66,19 @@ func addPHPDeployCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
status, err := Deploy(ctx, opts)
|
status, err := Deploy(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.deploy_failed"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.deploy_failed"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
printDeploymentStatus(status)
|
printDeploymentStatus(status)
|
||||||
|
|
||||||
if deployWait {
|
if deployWait {
|
||||||
if IsDeploymentSuccessful(status.Status) {
|
if IsDeploymentSuccessful(status.Status) {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Deployment completed"}))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Deployment completed"}))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy.warning_status", map[string]interface{}{"Status": status.Status}))
|
cli.Print("\n%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy.warning_status", map[string]interface{}{"Status": status.Status}))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy.triggered"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy.triggered"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -106,7 +105,7 @@ func addPHPDeployStatusCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := EnvProduction
|
env := EnvProduction
|
||||||
|
|
@ -114,7 +113,7 @@ func addPHPDeployStatusCommand(parent *cobra.Command) {
|
||||||
env = EnvStaging
|
env = EnvStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.ProgressSubject("check", "deployment status"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.ProgressSubject("check", "deployment status"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -126,7 +125,7 @@ func addPHPDeployStatusCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
status, err := DeployStatus(ctx, opts)
|
status, err := DeployStatus(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "status"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "status"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
printDeploymentStatus(status)
|
printDeploymentStatus(status)
|
||||||
|
|
@ -155,7 +154,7 @@ func addPHPDeployRollbackCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := EnvProduction
|
env := EnvProduction
|
||||||
|
|
@ -163,7 +162,7 @@ func addPHPDeployRollbackCommand(parent *cobra.Command) {
|
||||||
env = EnvStaging
|
env = EnvStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy_rollback.rolling_back", map[string]interface{}{"Environment": env}))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy_rollback.rolling_back", map[string]interface{}{"Environment": env}))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -176,19 +175,19 @@ func addPHPDeployRollbackCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
status, err := Rollback(ctx, opts)
|
status, err := Rollback(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.rollback_failed"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.rollback_failed"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
printDeploymentStatus(status)
|
printDeploymentStatus(status)
|
||||||
|
|
||||||
if rollbackWait {
|
if rollbackWait {
|
||||||
if IsDeploymentSuccessful(status.Status) {
|
if IsDeploymentSuccessful(status.Status) {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Rollback completed"}))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Rollback completed"}))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy_rollback.warning_status", map[string]interface{}{"Status": status.Status}))
|
cli.Print("\n%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.deploy_rollback.warning_status", map[string]interface{}{"Status": status.Status}))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy_rollback.triggered"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.deploy_rollback.triggered"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -215,7 +214,7 @@ func addPHPDeployListCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := EnvProduction
|
env := EnvProduction
|
||||||
|
|
@ -228,17 +227,17 @@ func addPHPDeployListCommand(parent *cobra.Command) {
|
||||||
limit = 10
|
limit = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy_list.recent", map[string]interface{}{"Environment": env}))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.deploy")), i18n.T("cmd.php.deploy_list.recent", map[string]interface{}{"Environment": env}))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
deployments, err := ListDeployments(ctx, cwd, env, limit)
|
deployments, err := ListDeployments(ctx, cwd, env, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.list", "deployments"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.list", "deployments"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(deployments) == 0 {
|
if len(deployments) == 0 {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.deploy_list.none_found"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.deploy_list.none_found"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,18 +265,18 @@ func printDeploymentStatus(status *DeploymentStatus) {
|
||||||
statusStyle = phpDeployFailedStyle
|
statusStyle = phpDeployFailedStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("status")), statusStyle.Render(status.Status))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("status")), statusStyle.Render(status.Status))
|
||||||
|
|
||||||
if status.ID != "" {
|
if status.ID != "" {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.id")), status.ID)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.id")), status.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.URL != "" {
|
if status.URL != "" {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("url")), linkStyle.Render(status.URL))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("url")), linkStyle.Render(status.URL))
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.Branch != "" {
|
if status.Branch != "" {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.branch")), status.Branch)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.branch")), status.Branch)
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.Commit != "" {
|
if status.Commit != "" {
|
||||||
|
|
@ -285,26 +284,26 @@ func printDeploymentStatus(status *DeploymentStatus) {
|
||||||
if len(commit) > 7 {
|
if len(commit) > 7 {
|
||||||
commit = commit[:7]
|
commit = commit[:7]
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.commit")), commit)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.commit")), commit)
|
||||||
if status.CommitMessage != "" {
|
if status.CommitMessage != "" {
|
||||||
// Truncate long messages
|
// Truncate long messages
|
||||||
msg := status.CommitMessage
|
msg := status.CommitMessage
|
||||||
if len(msg) > 60 {
|
if len(msg) > 60 {
|
||||||
msg = msg[:57] + "..."
|
msg = msg[:57] + "..."
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.message")), msg)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.message")), msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !status.StartedAt.IsZero() {
|
if !status.StartedAt.IsZero() {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("started")), status.StartedAt.Format(time.RFC3339))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("started")), status.StartedAt.Format(time.RFC3339))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !status.CompletedAt.IsZero() {
|
if !status.CompletedAt.IsZero() {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.completed")), status.CompletedAt.Format(time.RFC3339))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.completed")), status.CompletedAt.Format(time.RFC3339))
|
||||||
if !status.StartedAt.IsZero() {
|
if !status.StartedAt.IsZero() {
|
||||||
duration := status.CompletedAt.Sub(status.StartedAt)
|
duration := status.CompletedAt.Sub(status.StartedAt)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.duration")), duration.Round(time.Second))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.duration")), duration.Round(time.Second))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -340,24 +339,23 @@ func printDeploymentSummary(index int, status *DeploymentStatus) {
|
||||||
age = i18n.TimeAgo(status.StartedAt)
|
age = i18n.TimeAgo(status.StartedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s %s",
|
cli.Print(" %s %s %s",
|
||||||
dimStyle.Render(fmt.Sprintf("#%d", index)),
|
dimStyle.Render(cli.Sprintf("#%d", index)),
|
||||||
statusStyle.Render(fmt.Sprintf("[%s]", status.Status)),
|
statusStyle.Render(cli.Sprintf("[%s]", status.Status)),
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if commit != "" {
|
if commit != "" {
|
||||||
fmt.Printf(" %s", commit)
|
cli.Print(" %s", commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
fmt.Printf(" - %s", msg)
|
cli.Print(" - %s", msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if age != "" {
|
if age != "" {
|
||||||
fmt.Printf(" %s", dimStyle.Render(fmt.Sprintf("(%s)", age)))
|
cli.Print(" %s", dimStyle.Render(cli.Sprintf("(%s)", age)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -12,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -68,7 +68,7 @@ type phpDevOptions struct {
|
||||||
func runPHPDev(opts phpDevOptions) error {
|
func runPHPDev(opts phpDevOptions) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.Err("failed to get working directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a Laravel project
|
// Check if this is a Laravel project
|
||||||
|
|
@ -82,15 +82,15 @@ func runPHPDev(opts phpDevOptions) error {
|
||||||
appName = "Laravel"
|
appName = "Laravel"
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.dev.starting", map[string]interface{}{"AppName": appName}))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.dev.starting", map[string]interface{}{"AppName": appName}))
|
||||||
|
|
||||||
// Detect services
|
// Detect services
|
||||||
services := DetectServices(cwd)
|
services := DetectServices(cwd)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.services")), i18n.T("cmd.php.dev.detected_services"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.services")), i18n.T("cmd.php.dev.detected_services"))
|
||||||
for _, svc := range services {
|
for _, svc := range services {
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("*"), svc)
|
cli.Print(" %s %s\n", successStyle.Render("*"), svc)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Setup options
|
// Setup options
|
||||||
port := opts.Port
|
port := opts.Port
|
||||||
|
|
@ -121,41 +121,41 @@ func runPHPDev(opts phpDevOptions) error {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-sigCh
|
<-sigCh
|
||||||
fmt.Printf("\n%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.dev.shutting_down"))
|
cli.Print("\n%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.dev.shutting_down"))
|
||||||
cancel()
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := server.Start(ctx, devOpts); err != nil {
|
if err := server.Start(ctx, devOpts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.start", "services"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.start", "services"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print status
|
// Print status
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(i18n.T("cmd.php.label.running")), i18n.T("cmd.php.dev.services_started"))
|
cli.Print("%s %s\n", successStyle.Render(i18n.T("cmd.php.label.running")), i18n.T("cmd.php.dev.services_started"))
|
||||||
printServiceStatuses(server.Status())
|
printServiceStatuses(server.Status())
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Print URLs
|
// Print URLs
|
||||||
appURL := GetLaravelAppURL(cwd)
|
appURL := GetLaravelAppURL(cwd)
|
||||||
if appURL == "" {
|
if appURL == "" {
|
||||||
if opts.HTTPS {
|
if opts.HTTPS {
|
||||||
appURL = fmt.Sprintf("https://localhost:%d", port)
|
appURL = cli.Sprintf("https://localhost:%d", port)
|
||||||
} else {
|
} else {
|
||||||
appURL = fmt.Sprintf("http://localhost:%d", port)
|
appURL = cli.Sprintf("http://localhost:%d", port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.app_url")), linkStyle.Render(appURL))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.app_url")), linkStyle.Render(appURL))
|
||||||
|
|
||||||
// Check for Vite
|
// Check for Vite
|
||||||
if !opts.NoVite && containsService(services, ServiceVite) {
|
if !opts.NoVite && containsService(services, ServiceVite) {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.vite")), linkStyle.Render("http://localhost:5173"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.vite")), linkStyle.Render("http://localhost:5173"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s\n\n", dimStyle.Render(i18n.T("cmd.php.dev.press_ctrl_c")))
|
cli.Print("\n%s\n\n", dimStyle.Render(i18n.T("cmd.php.dev.press_ctrl_c")))
|
||||||
|
|
||||||
// Stream unified logs
|
// Stream unified logs
|
||||||
logsReader, err := server.Logs("", true)
|
logsReader, err := server.Logs("", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("i18n.fail.get", "logs"))
|
cli.Print("%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("i18n.fail.get", "logs"))
|
||||||
} else {
|
} else {
|
||||||
defer logsReader.Close()
|
defer logsReader.Close()
|
||||||
|
|
||||||
|
|
@ -174,10 +174,10 @@ func runPHPDev(opts phpDevOptions) error {
|
||||||
shutdown:
|
shutdown:
|
||||||
// Stop services
|
// Stop services
|
||||||
if err := server.Stop(); err != nil {
|
if err := server.Stop(); err != nil {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.dev.stop_error", map[string]interface{}{"Error": err}))
|
cli.Print("%s %s\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.dev.stop_error", map[string]interface{}{"Error": err}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.dev.all_stopped"))
|
cli.Print("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.dev.all_stopped"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +217,7 @@ func runPHPLogs(service string, follow bool) error {
|
||||||
|
|
||||||
logsReader, err := server.Logs(service, follow)
|
logsReader, err := server.Logs(service, follow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "logs"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "logs"), err)
|
||||||
}
|
}
|
||||||
defer logsReader.Close()
|
defer logsReader.Close()
|
||||||
|
|
||||||
|
|
@ -264,16 +264,16 @@ func runPHPStop() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.stop.stopping"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.stop.stopping"))
|
||||||
|
|
||||||
// We need to find running processes
|
// We need to find running processes
|
||||||
// This is a simplified version - in practice you'd want to track PIDs
|
// This is a simplified version - in practice you'd want to track PIDs
|
||||||
server := NewDevServer(Options{Dir: cwd})
|
server := NewDevServer(Options{Dir: cwd})
|
||||||
if err := server.Stop(); err != nil {
|
if err := server.Stop(); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.stop", "services"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.stop", "services"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.dev.all_stopped"))
|
cli.Print("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.dev.all_stopped"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,24 +304,24 @@ func runPHPStatus() error {
|
||||||
appName = "Laravel"
|
appName = "Laravel"
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.Label("project")), appName)
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.Label("project")), appName)
|
||||||
|
|
||||||
// Detect available services
|
// Detect available services
|
||||||
services := DetectServices(cwd)
|
services := DetectServices(cwd)
|
||||||
fmt.Printf("%s\n", dimStyle.Render(i18n.T("cmd.php.status.detected_services")))
|
cli.Print("%s\n", dimStyle.Render(i18n.T("cmd.php.status.detected_services")))
|
||||||
for _, svc := range services {
|
for _, svc := range services {
|
||||||
style := getServiceStyle(string(svc))
|
style := getServiceStyle(string(svc))
|
||||||
fmt.Printf(" %s %s\n", style.Render("*"), svc)
|
cli.Print(" %s %s\n", style.Render("*"), svc)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Package manager
|
// Package manager
|
||||||
pm := DetectPackageManager(cwd)
|
pm := DetectPackageManager(cwd)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.package_manager")), pm)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.package_manager")), pm)
|
||||||
|
|
||||||
// FrankenPHP status
|
// FrankenPHP status
|
||||||
if IsFrankenPHPProject(cwd) {
|
if IsFrankenPHPProject(cwd) {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.octane_server")), "FrankenPHP")
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.octane_server")), "FrankenPHP")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSL status
|
// SSL status
|
||||||
|
|
@ -329,9 +329,9 @@ func runPHPStatus() error {
|
||||||
if appURL != "" {
|
if appURL != "" {
|
||||||
domain := ExtractDomainFromURL(appURL)
|
domain := ExtractDomainFromURL(appURL)
|
||||||
if CertsExist(domain, SSLOptions{}) {
|
if CertsExist(domain, SSLOptions{}) {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.ssl_certs")), successStyle.Render(i18n.T("cmd.php.status.ssl_installed")))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.ssl_certs")), successStyle.Render(i18n.T("cmd.php.status.ssl_installed")))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.ssl_certs")), dimStyle.Render(i18n.T("cmd.php.status.ssl_not_setup")))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.status.ssl_certs")), dimStyle.Render(i18n.T("cmd.php.status.ssl_not_setup")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,35 +373,35 @@ func runPHPSSL(domain string) error {
|
||||||
|
|
||||||
// Check if mkcert is installed
|
// Check if mkcert is installed
|
||||||
if !IsMkcertInstalled() {
|
if !IsMkcertInstalled() {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.ssl.mkcert_not_installed"))
|
cli.Print("%s %s\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.ssl.mkcert_not_installed"))
|
||||||
fmt.Printf("\n%s\n", i18n.T("common.hint.install_with"))
|
cli.Print("\n%s\n", i18n.T("common.hint.install_with"))
|
||||||
fmt.Printf(" %s\n", i18n.T("cmd.php.ssl.install_macos"))
|
cli.Print(" %s\n", i18n.T("cmd.php.ssl.install_macos"))
|
||||||
fmt.Printf(" %s\n", i18n.T("cmd.php.ssl.install_linux"))
|
cli.Print(" %s\n", i18n.T("cmd.php.ssl.install_linux"))
|
||||||
return errors.New(i18n.T("cmd.php.error.mkcert_not_installed"))
|
return errors.New(i18n.T("cmd.php.error.mkcert_not_installed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render("SSL:"), i18n.T("cmd.php.ssl.setting_up", map[string]interface{}{"Domain": domain}))
|
cli.Print("%s %s\n", dimStyle.Render("SSL:"), i18n.T("cmd.php.ssl.setting_up", map[string]interface{}{"Domain": domain}))
|
||||||
|
|
||||||
// Check if certs already exist
|
// Check if certs already exist
|
||||||
if CertsExist(domain, SSLOptions{}) {
|
if CertsExist(domain, SSLOptions{}) {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("skip")), i18n.T("cmd.php.ssl.certs_exist"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("skip")), i18n.T("cmd.php.ssl.certs_exist"))
|
||||||
|
|
||||||
certFile, keyFile, _ := CertPaths(domain, SSLOptions{})
|
certFile, keyFile, _ := CertPaths(domain, SSLOptions{})
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.cert_label")), certFile)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.cert_label")), certFile)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.key_label")), keyFile)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.key_label")), keyFile)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup SSL
|
// Setup SSL
|
||||||
if err := SetupSSL(domain, SSLOptions{}); err != nil {
|
if err := SetupSSL(domain, SSLOptions{}); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.setup", "SSL"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.setup", "SSL"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
certFile, keyFile, _ := CertPaths(domain, SSLOptions{})
|
certFile, keyFile, _ := CertPaths(domain, SSLOptions{})
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.ssl.certs_created"))
|
cli.Print("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.ssl.certs_created"))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.cert_label")), certFile)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.cert_label")), certFile)
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.key_label")), keyFile)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.ssl.key_label")), keyFile)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -418,16 +418,16 @@ func printServiceStatuses(statuses []ServiceStatus) {
|
||||||
} else if s.Running {
|
} else if s.Running {
|
||||||
statusText = phpStatusRunning.Render(i18n.T("cmd.php.status.running"))
|
statusText = phpStatusRunning.Render(i18n.T("cmd.php.status.running"))
|
||||||
if s.Port > 0 {
|
if s.Port > 0 {
|
||||||
statusText += dimStyle.Render(fmt.Sprintf(" (%s)", i18n.T("cmd.php.status.port", map[string]interface{}{"Port": s.Port})))
|
statusText += dimStyle.Render(cli.Sprintf(" (%s)", i18n.T("cmd.php.status.port", map[string]interface{}{"Port": s.Port})))
|
||||||
}
|
}
|
||||||
if s.PID > 0 {
|
if s.PID > 0 {
|
||||||
statusText += dimStyle.Render(fmt.Sprintf(" [%s]", i18n.T("cmd.php.status.pid", map[string]interface{}{"PID": s.PID})))
|
statusText += dimStyle.Render(cli.Sprintf(" [%s]", i18n.T("cmd.php.status.pid", map[string]interface{}{"PID": s.PID})))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
statusText = phpStatusStopped.Render(i18n.T("cmd.php.status.stopped"))
|
statusText = phpStatusStopped.Render(i18n.T("cmd.php.status.stopped"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s\n", style.Render(s.Name+":"), statusText)
|
cli.Print(" %s %s\n", style.Render(s.Name+":"), statusText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -460,13 +460,13 @@ func printColoredLog(line string) {
|
||||||
line = strings.TrimPrefix(line, "[Redis] ")
|
line = strings.TrimPrefix(line, "[Redis] ")
|
||||||
} else {
|
} else {
|
||||||
// Unknown service, print as-is
|
// Unknown service, print as-is
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(timestamp), line)
|
cli.Print("%s %s\n", dimStyle.Render(timestamp), line)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s %s\n",
|
cli.Print("%s %s %s\n",
|
||||||
dimStyle.Render(timestamp),
|
dimStyle.Render(timestamp),
|
||||||
style.Render(fmt.Sprintf("[%s]", serviceName)),
|
style.Render(cli.Sprintf("[%s]", serviceName)),
|
||||||
line,
|
line,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package php
|
package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -31,16 +31,16 @@ func addPHPPackagesLinkCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.link.linking"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.link.linking"))
|
||||||
|
|
||||||
if err := LinkPackages(cwd, args); err != nil {
|
if err := LinkPackages(cwd, args); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.link", "packages"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.link", "packages"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.link.done"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.link.done"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -57,16 +57,16 @@ func addPHPPackagesUnlinkCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.unlink.unlinking"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.unlink.unlinking"))
|
||||||
|
|
||||||
if err := UnlinkPackages(cwd, args); err != nil {
|
if err := UnlinkPackages(cwd, args); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.unlink", "packages"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.unlink", "packages"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.unlink.done"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.unlink.done"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -82,16 +82,16 @@ func addPHPPackagesUpdateCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.update.updating"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.update.updating"))
|
||||||
|
|
||||||
if err := UpdatePackages(cwd, args); err != nil {
|
if err := UpdatePackages(cwd, args); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.update_packages"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.update_packages"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.update.done"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.packages.update.done"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -107,20 +107,20 @@ func addPHPPackagesListCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
packages, err := ListLinkedPackages(cwd)
|
packages, err := ListLinkedPackages(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.list", "packages"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.list", "packages"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(packages) == 0 {
|
if len(packages) == 0 {
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.list.none_found"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.list.none_found"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.list.linked"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.T("cmd.php.packages.list.linked"))
|
||||||
|
|
||||||
for _, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
name := pkg.Name
|
name := pkg.Name
|
||||||
|
|
@ -132,10 +132,10 @@ func addPHPPackagesListCommand(parent *cobra.Command) {
|
||||||
version = "dev"
|
version = "dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s\n", successStyle.Render("*"), name)
|
cli.Print(" %s %s\n", successStyle.Render("*"), name)
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("path")), pkg.Path)
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("path")), pkg.Path)
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("version")), version)
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("version")), version)
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/framework"
|
"github.com/host-uk/core/pkg/framework"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/host-uk/core/pkg/process"
|
"github.com/host-uk/core/pkg/process"
|
||||||
|
|
@ -32,12 +32,12 @@ func NewQARunner(dir string, fix bool) (*QARunner, error) {
|
||||||
framework.WithName("process", process.NewService(process.Options{})),
|
framework.WithName("process", process.NewService(process.Options{})),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create process service: %w", err)
|
return nil, cli.WrapVerb(err, "create", "process service")
|
||||||
}
|
}
|
||||||
|
|
||||||
svc, err := framework.ServiceFor[*process.Service](core, "process")
|
svc, err := framework.ServiceFor[*process.Service](core, "process")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get process service: %w", err)
|
return nil, cli.WrapVerb(err, "get", "process service")
|
||||||
}
|
}
|
||||||
|
|
||||||
runner := &QARunner{
|
runner := &QARunner{
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ package php
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
"github.com/host-uk/core/pkg/i18n"
|
"github.com/host-uk/core/pkg/i18n"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -27,14 +27,14 @@ func addPHPTestCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
return errors.New(i18n.T("cmd.php.error.not_php"))
|
return errors.New(i18n.T("cmd.php.error.not_php"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.ProgressSubject("run", "tests"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.ProgressSubject("run", "tests"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ func addPHPTestCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RunTests(ctx, opts); err != nil {
|
if err := RunTests(ctx, opts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.run", "tests"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.run", "tests"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -79,7 +79,7 @@ func addPHPFmtCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
|
|
@ -98,7 +98,7 @@ func addPHPFmtCommand(parent *cobra.Command) {
|
||||||
} else {
|
} else {
|
||||||
msg = i18n.ProgressSubject("check", "code style")
|
msg = i18n.ProgressSubject("check", "code style")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), msg)
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), msg)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -116,15 +116,15 @@ func addPHPFmtCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
if err := Format(ctx, opts); err != nil {
|
if err := Format(ctx, opts); err != nil {
|
||||||
if fmtFix {
|
if fmtFix {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.fmt_failed"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.fmt_failed"), err)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.fmt_issues"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.fmt_issues"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fmtFix {
|
if fmtFix {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Code formatted"}))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Code formatted"}))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.fmt.no_issues"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.fmt.no_issues"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -150,7 +150,7 @@ func addPHPStanCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
|
|
@ -163,7 +163,7 @@ func addPHPStanCommand(parent *cobra.Command) {
|
||||||
return errors.New(i18n.T("cmd.php.analyse.no_analyser"))
|
return errors.New(i18n.T("cmd.php.analyse.no_analyser"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.ProgressSubject("run", "static analysis"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.php")), i18n.ProgressSubject("run", "static analysis"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -180,10 +180,10 @@ func addPHPStanCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Analyse(ctx, opts); err != nil {
|
if err := Analyse(ctx, opts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.analysis_issues"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.analysis_issues"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.result.no_issues"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.result.no_issues"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +213,7 @@ func addPHPPsalmCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
|
|
@ -223,9 +223,9 @@ func addPHPPsalmCommand(parent *cobra.Command) {
|
||||||
// Check if Psalm is available
|
// Check if Psalm is available
|
||||||
_, found := DetectPsalm(cwd)
|
_, found := DetectPsalm(cwd)
|
||||||
if !found {
|
if !found {
|
||||||
fmt.Printf("%s %s\n\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.psalm.not_found"))
|
cli.Print("%s %s\n\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.psalm.not_found"))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.T("cmd.php.psalm.install"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.T("cmd.php.psalm.install"))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.setup")), i18n.T("cmd.php.psalm.setup"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.setup")), i18n.T("cmd.php.psalm.setup"))
|
||||||
return errors.New(i18n.T("cmd.php.error.psalm_not_installed"))
|
return errors.New(i18n.T("cmd.php.error.psalm_not_installed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,7 +235,7 @@ func addPHPPsalmCommand(parent *cobra.Command) {
|
||||||
} else {
|
} else {
|
||||||
msg = i18n.T("cmd.php.psalm.analysing")
|
msg = i18n.T("cmd.php.psalm.analysing")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.psalm")), msg)
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.psalm")), msg)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -249,10 +249,10 @@ func addPHPPsalmCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RunPsalm(ctx, opts); err != nil {
|
if err := RunPsalm(ctx, opts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.psalm_issues"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.psalm_issues"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.result.no_issues"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.result.no_issues"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -278,14 +278,14 @@ func addPHPAuditCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
return errors.New(i18n.T("cmd.php.error.not_php"))
|
return errors.New(i18n.T("cmd.php.error.not_php"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.audit")), i18n.T("cmd.php.audit.scanning"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.audit")), i18n.T("cmd.php.audit.scanning"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -296,7 +296,7 @@ func addPHPAuditCommand(parent *cobra.Command) {
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.audit_failed"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.audit_failed"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print results
|
// Print results
|
||||||
|
|
@ -317,7 +317,7 @@ func addPHPAuditCommand(parent *cobra.Command) {
|
||||||
totalVulns += result.Vulnerabilities
|
totalVulns += result.Vulnerabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s %s\n", icon, dimStyle.Render(result.Tool+":"), status)
|
cli.Print(" %s %s %s\n", icon, dimStyle.Render(result.Tool+":"), status)
|
||||||
|
|
||||||
// Show advisories
|
// Show advisories
|
||||||
for _, adv := range result.Advisories {
|
for _, adv := range result.Advisories {
|
||||||
|
|
@ -326,18 +326,18 @@ func addPHPAuditCommand(parent *cobra.Command) {
|
||||||
severity = "unknown"
|
severity = "unknown"
|
||||||
}
|
}
|
||||||
sevStyle := getSeverityStyle(severity)
|
sevStyle := getSeverityStyle(severity)
|
||||||
fmt.Printf(" %s %s\n", sevStyle.Render("["+severity+"]"), adv.Package)
|
cli.Print(" %s %s\n", sevStyle.Render("["+severity+"]"), adv.Package)
|
||||||
if adv.Title != "" {
|
if adv.Title != "" {
|
||||||
fmt.Printf(" %s\n", dimStyle.Render(adv.Title))
|
cli.Print(" %s\n", dimStyle.Render(adv.Title))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
if totalVulns > 0 {
|
if totalVulns > 0 {
|
||||||
fmt.Printf("%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.audit.found_vulns", map[string]interface{}{"Count": totalVulns}))
|
cli.Print("%s %s\n", errorStyle.Render(i18n.Label("warning")), i18n.T("cmd.php.audit.found_vulns", map[string]interface{}{"Count": totalVulns}))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("fix")), i18n.T("common.hint.fix_deps"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("fix")), i18n.T("common.hint.fix_deps"))
|
||||||
return errors.New(i18n.T("cmd.php.error.vulns_found"))
|
return errors.New(i18n.T("cmd.php.error.vulns_found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,7 +345,7 @@ func addPHPAuditCommand(parent *cobra.Command) {
|
||||||
return errors.New(i18n.T("cmd.php.audit.completed_errors"))
|
return errors.New(i18n.T("cmd.php.audit.completed_errors"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.audit.all_secure"))
|
cli.Print("%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.audit.all_secure"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -371,14 +371,14 @@ func addPHPSecurityCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
return errors.New(i18n.T("cmd.php.error.not_php"))
|
return errors.New(i18n.T("cmd.php.error.not_php"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.security")), i18n.ProgressSubject("run", "security checks"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.security")), i18n.ProgressSubject("run", "security checks"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -391,7 +391,7 @@ func addPHPSecurityCommand(parent *cobra.Command) {
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.security_failed"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.security_failed"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print results by category
|
// Print results by category
|
||||||
|
|
@ -400,10 +400,10 @@ func addPHPSecurityCommand(parent *cobra.Command) {
|
||||||
category := strings.Split(check.ID, "_")[0]
|
category := strings.Split(check.ID, "_")[0]
|
||||||
if category != currentCategory {
|
if category != currentCategory {
|
||||||
if currentCategory != "" {
|
if currentCategory != "" {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
currentCategory = category
|
currentCategory = category
|
||||||
fmt.Printf(" %s\n", dimStyle.Render(strings.ToUpper(category)+i18n.T("cmd.php.security.checks_suffix")))
|
cli.Print(" %s\n", dimStyle.Render(strings.ToUpper(category)+i18n.T("cmd.php.security.checks_suffix")))
|
||||||
}
|
}
|
||||||
|
|
||||||
icon := successStyle.Render("✓")
|
icon := successStyle.Render("✓")
|
||||||
|
|
@ -411,32 +411,32 @@ func addPHPSecurityCommand(parent *cobra.Command) {
|
||||||
icon = getSeverityStyle(check.Severity).Render("✗")
|
icon = getSeverityStyle(check.Severity).Render("✗")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s\n", icon, check.Name)
|
cli.Print(" %s %s\n", icon, check.Name)
|
||||||
if !check.Passed && check.Message != "" {
|
if !check.Passed && check.Message != "" {
|
||||||
fmt.Printf(" %s\n", dimStyle.Render(check.Message))
|
cli.Print(" %s\n", dimStyle.Render(check.Message))
|
||||||
if check.Fix != "" {
|
if check.Fix != "" {
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render(i18n.Label("fix")), check.Fix)
|
cli.Print(" %s %s\n", dimStyle.Render(i18n.Label("fix")), check.Fix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Print summary
|
// Print summary
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("summary")), i18n.T("cmd.php.security.summary"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("summary")), i18n.T("cmd.php.security.summary"))
|
||||||
fmt.Printf(" %s %d/%d\n", dimStyle.Render(i18n.T("cmd.php.security.passed")), result.Summary.Passed, result.Summary.Total)
|
cli.Print(" %s %d/%d\n", dimStyle.Render(i18n.T("cmd.php.security.passed")), result.Summary.Passed, result.Summary.Total)
|
||||||
|
|
||||||
if result.Summary.Critical > 0 {
|
if result.Summary.Critical > 0 {
|
||||||
fmt.Printf(" %s %d\n", phpSecurityCriticalStyle.Render(i18n.T("cmd.php.security.critical")), result.Summary.Critical)
|
cli.Print(" %s %d\n", phpSecurityCriticalStyle.Render(i18n.T("cmd.php.security.critical")), result.Summary.Critical)
|
||||||
}
|
}
|
||||||
if result.Summary.High > 0 {
|
if result.Summary.High > 0 {
|
||||||
fmt.Printf(" %s %d\n", phpSecurityHighStyle.Render(i18n.T("cmd.php.security.high")), result.Summary.High)
|
cli.Print(" %s %d\n", phpSecurityHighStyle.Render(i18n.T("cmd.php.security.high")), result.Summary.High)
|
||||||
}
|
}
|
||||||
if result.Summary.Medium > 0 {
|
if result.Summary.Medium > 0 {
|
||||||
fmt.Printf(" %s %d\n", phpSecurityMediumStyle.Render(i18n.T("cmd.php.security.medium")), result.Summary.Medium)
|
cli.Print(" %s %d\n", phpSecurityMediumStyle.Render(i18n.T("cmd.php.security.medium")), result.Summary.Medium)
|
||||||
}
|
}
|
||||||
if result.Summary.Low > 0 {
|
if result.Summary.Low > 0 {
|
||||||
fmt.Printf(" %s %d\n", phpSecurityLowStyle.Render(i18n.T("cmd.php.security.low")), result.Summary.Low)
|
cli.Print(" %s %d\n", phpSecurityLowStyle.Render(i18n.T("cmd.php.security.low")), result.Summary.Low)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Summary.Critical > 0 || result.Summary.High > 0 {
|
if result.Summary.Critical > 0 || result.Summary.High > 0 {
|
||||||
|
|
@ -469,7 +469,7 @@ func addPHPQACommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
|
|
@ -486,20 +486,20 @@ func addPHPQACommand(parent *cobra.Command) {
|
||||||
stages := GetQAStages(opts)
|
stages := GetQAStages(opts)
|
||||||
|
|
||||||
// Print header
|
// Print header
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.Label("qa")), i18n.ProgressSubject("run", "QA pipeline"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.Label("qa")), i18n.ProgressSubject("run", "QA pipeline"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Create QA runner using pkg/process
|
// Create QA runner using pkg/process
|
||||||
runner, err := NewQARunner(cwd, qaFix)
|
runner, err := NewQARunner(cwd, qaFix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.create", "QA runner"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.create", "QA runner"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all checks with dependency ordering
|
// Run all checks with dependency ordering
|
||||||
result, err := runner.Run(ctx, stages)
|
result, err := runner.Run(ctx, stages)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.run", "QA checks"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.run", "QA checks"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display results by stage
|
// Display results by stage
|
||||||
|
|
@ -509,10 +509,10 @@ func addPHPQACommand(parent *cobra.Command) {
|
||||||
stage := getCheckStage(checkResult.Name, stages, cwd)
|
stage := getCheckStage(checkResult.Name, stages, cwd)
|
||||||
if stage != currentStage {
|
if stage != currentStage {
|
||||||
if currentStage != "" {
|
if currentStage != "" {
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
}
|
}
|
||||||
currentStage = stage
|
currentStage = stage
|
||||||
fmt.Printf("%s\n", phpQAStageStyle.Render("── "+strings.ToUpper(stage)+" ──"))
|
cli.Print("%s\n", phpQAStageStyle.Render("── "+strings.ToUpper(stage)+" ──"))
|
||||||
}
|
}
|
||||||
|
|
||||||
icon := phpQAPassedStyle.Render("✓")
|
icon := phpQAPassedStyle.Render("✓")
|
||||||
|
|
@ -525,21 +525,21 @@ func addPHPQACommand(parent *cobra.Command) {
|
||||||
status = phpQAFailedStyle.Render(i18n.T("i18n.done.fail"))
|
status = phpQAFailedStyle.Render(i18n.T("i18n.done.fail"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(" %s %s %s %s\n", icon, checkResult.Name, status, dimStyle.Render(checkResult.Duration))
|
cli.Print(" %s %s %s %s\n", icon, checkResult.Name, status, dimStyle.Render(checkResult.Duration))
|
||||||
}
|
}
|
||||||
fmt.Println()
|
cli.Line("")
|
||||||
|
|
||||||
// Print summary
|
// Print summary
|
||||||
if result.Passed {
|
if result.Passed {
|
||||||
fmt.Printf("%s %s\n", phpQAPassedStyle.Render("QA PASSED:"), i18n.T("i18n.count.check", result.PassedCount)+" "+i18n.T("i18n.done.pass"))
|
cli.Print("%s %s\n", phpQAPassedStyle.Render("QA PASSED:"), i18n.T("i18n.count.check", result.PassedCount)+" "+i18n.T("i18n.done.pass"))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("i18n.label.duration")), result.Duration)
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("i18n.label.duration")), result.Duration)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n\n", phpQAFailedStyle.Render("QA FAILED:"), i18n.T("i18n.count.check", result.PassedCount)+"/"+fmt.Sprint(len(result.Results))+" "+i18n.T("i18n.done.pass"))
|
cli.Print("%s %s\n\n", phpQAFailedStyle.Render("QA FAILED:"), i18n.T("i18n.count.check", result.PassedCount)+"/"+cli.Sprint(len(result.Results))+" "+i18n.T("i18n.done.pass"))
|
||||||
|
|
||||||
// Show what needs fixing
|
// Show what needs fixing
|
||||||
fmt.Printf("%s\n", dimStyle.Render(i18n.T("i18n.label.fix")))
|
cli.Print("%s\n", dimStyle.Render(i18n.T("i18n.label.fix")))
|
||||||
for _, checkResult := range result.Results {
|
for _, checkResult := range result.Results {
|
||||||
if checkResult.Passed || checkResult.Skipped {
|
if checkResult.Passed || checkResult.Skipped {
|
||||||
continue
|
continue
|
||||||
|
|
@ -549,13 +549,13 @@ func addPHPQACommand(parent *cobra.Command) {
|
||||||
if issue == "" {
|
if issue == "" {
|
||||||
issue = "issues found"
|
issue = "issues found"
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", phpQAFailedStyle.Render("*"), checkResult.Name+": "+issue)
|
cli.Print(" %s %s\n", phpQAFailedStyle.Render("*"), checkResult.Name+": "+issue)
|
||||||
if fixCmd != "" {
|
if fixCmd != "" {
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render("->"), fixCmd)
|
cli.Print(" %s %s\n", dimStyle.Render("->"), fixCmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("%s", i18n.T("i18n.fail.run", "QA pipeline"))
|
return cli.Err("%s", i18n.T("i18n.fail.run", "QA pipeline"))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -619,7 +619,7 @@ func addPHPRectorCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
|
|
@ -628,9 +628,9 @@ func addPHPRectorCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
// Check if Rector is available
|
// Check if Rector is available
|
||||||
if !DetectRector(cwd) {
|
if !DetectRector(cwd) {
|
||||||
fmt.Printf("%s %s\n\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.rector.not_found"))
|
cli.Print("%s %s\n\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.rector.not_found"))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.T("cmd.php.rector.install"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.T("cmd.php.rector.install"))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.setup")), i18n.T("cmd.php.rector.setup"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.setup")), i18n.T("cmd.php.rector.setup"))
|
||||||
return errors.New(i18n.T("cmd.php.error.rector_not_installed"))
|
return errors.New(i18n.T("cmd.php.error.rector_not_installed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -640,7 +640,7 @@ func addPHPRectorCommand(parent *cobra.Command) {
|
||||||
} else {
|
} else {
|
||||||
msg = i18n.T("cmd.php.rector.analysing")
|
msg = i18n.T("cmd.php.rector.analysing")
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.rector")), msg)
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.rector")), msg)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -654,17 +654,17 @@ func addPHPRectorCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
if err := RunRector(ctx, opts); err != nil {
|
if err := RunRector(ctx, opts); err != nil {
|
||||||
if rectorFix {
|
if rectorFix {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.rector_failed"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.rector_failed"), err)
|
||||||
}
|
}
|
||||||
// Dry-run returns non-zero if changes would be made
|
// Dry-run returns non-zero if changes would be made
|
||||||
fmt.Printf("\n%s %s\n", phpQAWarningStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.rector.changes_suggested"))
|
cli.Print("\n%s %s\n", phpQAWarningStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.rector.changes_suggested"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if rectorFix {
|
if rectorFix {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Code refactored"}))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("common.success.completed", map[string]any{"Action": "Code refactored"}))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.rector.no_changes"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.rector.no_changes"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
@ -693,7 +693,7 @@ func addPHPInfectionCommand(parent *cobra.Command) {
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !IsPHPProject(cwd) {
|
if !IsPHPProject(cwd) {
|
||||||
|
|
@ -702,13 +702,13 @@ func addPHPInfectionCommand(parent *cobra.Command) {
|
||||||
|
|
||||||
// Check if Infection is available
|
// Check if Infection is available
|
||||||
if !DetectInfection(cwd) {
|
if !DetectInfection(cwd) {
|
||||||
fmt.Printf("%s %s\n\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.infection.not_found"))
|
cli.Print("%s %s\n\n", errorStyle.Render(i18n.Label("error")), i18n.T("cmd.php.infection.not_found"))
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.T("cmd.php.infection.install"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.Label("install")), i18n.T("cmd.php.infection.install"))
|
||||||
return errors.New(i18n.T("cmd.php.error.infection_not_installed"))
|
return errors.New(i18n.T("cmd.php.error.infection_not_installed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.infection")), i18n.ProgressSubject("run", "mutation testing"))
|
cli.Print("%s %s\n", dimStyle.Render(i18n.T("cmd.php.label.infection")), i18n.ProgressSubject("run", "mutation testing"))
|
||||||
fmt.Printf("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.infection.note"))
|
cli.Print("%s %s\n\n", dimStyle.Render(i18n.T("cmd.php.label.info")), i18n.T("cmd.php.infection.note"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
|
@ -723,10 +723,10 @@ func addPHPInfectionCommand(parent *cobra.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := RunInfection(ctx, opts); err != nil {
|
if err := RunInfection(ctx, opts); err != nil {
|
||||||
return fmt.Errorf("%s: %w", i18n.T("cmd.php.error.infection_failed"), err)
|
return cli.Err("%s: %w", i18n.T("cmd.php.error.infection_failed"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.infection.complete"))
|
cli.Print("\n%s %s\n", successStyle.Render(i18n.Label("done")), i18n.T("cmd.php.infection.complete"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DockerBuildOptions configures Docker image building for PHP projects.
|
// DockerBuildOptions configures Docker image building for PHP projects.
|
||||||
|
|
@ -94,14 +95,14 @@ func BuildDocker(ctx context.Context, opts DockerBuildOptions) error {
|
||||||
if opts.ProjectDir == "" {
|
if opts.ProjectDir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.ProjectDir = cwd
|
opts.ProjectDir = cwd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate project directory
|
// Validate project directory
|
||||||
if !IsPHPProject(opts.ProjectDir) {
|
if !IsPHPProject(opts.ProjectDir) {
|
||||||
return fmt.Errorf("not a PHP project: %s (missing composer.json)", opts.ProjectDir)
|
return cli.Err("not a PHP project: %s (missing composer.json)", opts.ProjectDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
|
|
@ -123,13 +124,13 @@ func BuildDocker(ctx context.Context, opts DockerBuildOptions) error {
|
||||||
// Generate Dockerfile
|
// Generate Dockerfile
|
||||||
content, err := GenerateDockerfile(opts.ProjectDir)
|
content, err := GenerateDockerfile(opts.ProjectDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate Dockerfile: %w", err)
|
return cli.WrapVerb(err, "generate", "Dockerfile")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to temporary file
|
// Write to temporary file
|
||||||
tempDockerfile = filepath.Join(opts.ProjectDir, "Dockerfile.core-generated")
|
tempDockerfile = filepath.Join(opts.ProjectDir, "Dockerfile.core-generated")
|
||||||
if err := os.WriteFile(tempDockerfile, []byte(content), 0644); err != nil {
|
if err := os.WriteFile(tempDockerfile, []byte(content), 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write Dockerfile: %w", err)
|
return cli.WrapVerb(err, "write", "Dockerfile")
|
||||||
}
|
}
|
||||||
defer os.Remove(tempDockerfile)
|
defer os.Remove(tempDockerfile)
|
||||||
|
|
||||||
|
|
@ -137,7 +138,7 @@ func BuildDocker(ctx context.Context, opts DockerBuildOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build Docker image
|
// Build Docker image
|
||||||
imageRef := fmt.Sprintf("%s:%s", opts.ImageName, opts.Tag)
|
imageRef := cli.Sprintf("%s:%s", opts.ImageName, opts.Tag)
|
||||||
|
|
||||||
args := []string{"build", "-t", imageRef, "-f", dockerfilePath}
|
args := []string{"build", "-t", imageRef, "-f", dockerfilePath}
|
||||||
|
|
||||||
|
|
@ -150,7 +151,7 @@ func BuildDocker(ctx context.Context, opts DockerBuildOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range opts.BuildArgs {
|
for key, value := range opts.BuildArgs {
|
||||||
args = append(args, "--build-arg", fmt.Sprintf("%s=%s", key, value))
|
args = append(args, "--build-arg", cli.Sprintf("%s=%s", key, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, opts.ProjectDir)
|
args = append(args, opts.ProjectDir)
|
||||||
|
|
@ -161,7 +162,7 @@ func BuildDocker(ctx context.Context, opts DockerBuildOptions) error {
|
||||||
cmd.Stderr = opts.Output
|
cmd.Stderr = opts.Output
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return fmt.Errorf("docker build failed: %w", err)
|
return cli.Wrap(err, "docker build failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -172,14 +173,14 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error {
|
||||||
if opts.ProjectDir == "" {
|
if opts.ProjectDir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.ProjectDir = cwd
|
opts.ProjectDir = cwd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate project directory
|
// Validate project directory
|
||||||
if !IsPHPProject(opts.ProjectDir) {
|
if !IsPHPProject(opts.ProjectDir) {
|
||||||
return fmt.Errorf("not a PHP project: %s (missing composer.json)", opts.ProjectDir)
|
return cli.Err("not a PHP project: %s (missing composer.json)", opts.ProjectDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
|
|
@ -199,7 +200,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error {
|
||||||
// Ensure output directory exists
|
// Ensure output directory exists
|
||||||
outputDir := filepath.Dir(opts.OutputPath)
|
outputDir := filepath.Dir(opts.OutputPath)
|
||||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create output directory: %w", err)
|
return cli.WrapVerb(err, "create", "output directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find linuxkit binary
|
// Find linuxkit binary
|
||||||
|
|
@ -211,7 +212,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error {
|
||||||
// Get template content
|
// Get template content
|
||||||
templateContent, err := getLinuxKitTemplate(opts.Template)
|
templateContent, err := getLinuxKitTemplate(opts.Template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get template: %w", err)
|
return cli.WrapVerb(err, "get", "template")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply variables
|
// Apply variables
|
||||||
|
|
@ -224,13 +225,13 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error {
|
||||||
|
|
||||||
content, err := applyTemplateVariables(templateContent, opts.Variables)
|
content, err := applyTemplateVariables(templateContent, opts.Variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to apply template variables: %w", err)
|
return cli.WrapVerb(err, "apply", "template variables")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write template to temp file
|
// Write template to temp file
|
||||||
tempYAML := filepath.Join(opts.ProjectDir, ".core-linuxkit.yml")
|
tempYAML := filepath.Join(opts.ProjectDir, ".core-linuxkit.yml")
|
||||||
if err := os.WriteFile(tempYAML, []byte(content), 0644); err != nil {
|
if err := os.WriteFile(tempYAML, []byte(content), 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write template: %w", err)
|
return cli.WrapVerb(err, "write", "template")
|
||||||
}
|
}
|
||||||
defer os.Remove(tempYAML)
|
defer os.Remove(tempYAML)
|
||||||
|
|
||||||
|
|
@ -248,7 +249,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error {
|
||||||
cmd.Stderr = opts.Output
|
cmd.Stderr = opts.Output
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return fmt.Errorf("linuxkit build failed: %w", err)
|
return cli.Wrap(err, "linuxkit build failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -257,7 +258,7 @@ func BuildLinuxKit(ctx context.Context, opts LinuxKitBuildOptions) error {
|
||||||
// ServeProduction runs a production PHP container.
|
// ServeProduction runs a production PHP container.
|
||||||
func ServeProduction(ctx context.Context, opts ServeOptions) error {
|
func ServeProduction(ctx context.Context, opts ServeOptions) error {
|
||||||
if opts.ImageName == "" {
|
if opts.ImageName == "" {
|
||||||
return fmt.Errorf("image name is required")
|
return cli.Err("image name is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set defaults
|
// Set defaults
|
||||||
|
|
@ -274,7 +275,7 @@ func ServeProduction(ctx context.Context, opts ServeOptions) error {
|
||||||
opts.Output = os.Stdout
|
opts.Output = os.Stdout
|
||||||
}
|
}
|
||||||
|
|
||||||
imageRef := fmt.Sprintf("%s:%s", opts.ImageName, opts.Tag)
|
imageRef := cli.Sprintf("%s:%s", opts.ImageName, opts.Tag)
|
||||||
|
|
||||||
args := []string{"run"}
|
args := []string{"run"}
|
||||||
|
|
||||||
|
|
@ -289,8 +290,8 @@ func ServeProduction(ctx context.Context, opts ServeOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port mappings
|
// Port mappings
|
||||||
args = append(args, "-p", fmt.Sprintf("%d:80", opts.Port))
|
args = append(args, "-p", cli.Sprintf("%d:80", opts.Port))
|
||||||
args = append(args, "-p", fmt.Sprintf("%d:443", opts.HTTPSPort))
|
args = append(args, "-p", cli.Sprintf("%d:443", opts.HTTPSPort))
|
||||||
|
|
||||||
// Environment file
|
// Environment file
|
||||||
if opts.EnvFile != "" {
|
if opts.EnvFile != "" {
|
||||||
|
|
@ -299,7 +300,7 @@ func ServeProduction(ctx context.Context, opts ServeOptions) error {
|
||||||
|
|
||||||
// Volume mounts
|
// Volume mounts
|
||||||
for hostPath, containerPath := range opts.Volumes {
|
for hostPath, containerPath := range opts.Volumes {
|
||||||
args = append(args, "-v", fmt.Sprintf("%s:%s", hostPath, containerPath))
|
args = append(args, "-v", cli.Sprintf("%s:%s", hostPath, containerPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, imageRef)
|
args = append(args, imageRef)
|
||||||
|
|
@ -311,10 +312,10 @@ func ServeProduction(ctx context.Context, opts ServeOptions) error {
|
||||||
if opts.Detach {
|
if opts.Detach {
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to start container: %w", err)
|
return cli.WrapVerb(err, "start", "container")
|
||||||
}
|
}
|
||||||
containerID := strings.TrimSpace(string(output))
|
containerID := strings.TrimSpace(string(output))
|
||||||
fmt.Fprintf(opts.Output, "Container started: %s\n", containerID[:12])
|
cli.Print("Container started: %s\n", containerID[:12])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,7 +325,7 @@ func ServeProduction(ctx context.Context, opts ServeOptions) error {
|
||||||
// Shell opens a shell in a running container.
|
// Shell opens a shell in a running container.
|
||||||
func Shell(ctx context.Context, containerID string) error {
|
func Shell(ctx context.Context, containerID string) error {
|
||||||
if containerID == "" {
|
if containerID == "" {
|
||||||
return fmt.Errorf("container ID is required")
|
return cli.Err("container ID is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve partial container ID
|
// Resolve partial container ID
|
||||||
|
|
@ -367,7 +368,7 @@ func lookupLinuxKit() (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("linuxkit not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit")
|
return "", cli.Err("linuxkit not found. Install with: brew install linuxkit (macOS) or see https://github.com/linuxkit/linuxkit")
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLinuxKitTemplate retrieves a LinuxKit template by name.
|
// getLinuxKitTemplate retrieves a LinuxKit template by name.
|
||||||
|
|
@ -379,7 +380,7 @@ func getLinuxKitTemplate(name string) (string, error) {
|
||||||
|
|
||||||
// Try to load from container package templates
|
// Try to load from container package templates
|
||||||
// This would integrate with github.com/host-uk/core/pkg/container
|
// This would integrate with github.com/host-uk/core/pkg/container
|
||||||
return "", fmt.Errorf("template not found: %s", name)
|
return "", cli.Err("template not found: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyTemplateVariables applies variable substitution to template content.
|
// applyTemplateVariables applies variable substitution to template content.
|
||||||
|
|
@ -397,7 +398,7 @@ func resolveDockerContainerID(ctx context.Context, partialID string) (string, er
|
||||||
cmd := exec.CommandContext(ctx, "docker", "ps", "-a", "--no-trunc", "--format", "{{.ID}}")
|
cmd := exec.CommandContext(ctx, "docker", "ps", "-a", "--no-trunc", "--format", "{{.ID}}")
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to list containers: %w", err)
|
return "", cli.WrapVerb(err, "list", "containers")
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
|
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||||
|
|
@ -411,11 +412,11 @@ func resolveDockerContainerID(ctx context.Context, partialID string) (string, er
|
||||||
|
|
||||||
switch len(matches) {
|
switch len(matches) {
|
||||||
case 0:
|
case 0:
|
||||||
return "", fmt.Errorf("no container found matching: %s", partialID)
|
return "", cli.Err("no container found matching: %s", partialID)
|
||||||
case 1:
|
case 1:
|
||||||
return matches[0], nil
|
return matches[0], nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("multiple containers match '%s', be more specific", partialID)
|
return "", cli.Err("multiple containers match '%s', be more specific", partialID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CoolifyClient is an HTTP client for the Coolify API.
|
// CoolifyClient is an HTTP client for the Coolify API.
|
||||||
|
|
@ -89,13 +90,13 @@ func LoadCoolifyConfigFromFile(path string) (*CoolifyConfig, error) {
|
||||||
// No .env file, just use env vars
|
// No .env file, just use env vars
|
||||||
return validateCoolifyConfig(config)
|
return validateCoolifyConfig(config)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to open .env file: %w", err)
|
return nil, cli.WrapVerb(err, "open", ".env file")
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
content, err := io.ReadAll(file)
|
content, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read .env file: %w", err)
|
return nil, cli.WrapVerb(err, "read", ".env file")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse .env file
|
// Parse .env file
|
||||||
|
|
@ -143,17 +144,17 @@ func LoadCoolifyConfigFromFile(path string) (*CoolifyConfig, error) {
|
||||||
// validateCoolifyConfig checks that required fields are set.
|
// validateCoolifyConfig checks that required fields are set.
|
||||||
func validateCoolifyConfig(config *CoolifyConfig) (*CoolifyConfig, error) {
|
func validateCoolifyConfig(config *CoolifyConfig) (*CoolifyConfig, error) {
|
||||||
if config.URL == "" {
|
if config.URL == "" {
|
||||||
return nil, fmt.Errorf("COOLIFY_URL is not set")
|
return nil, cli.Err("COOLIFY_URL is not set")
|
||||||
}
|
}
|
||||||
if config.Token == "" {
|
if config.Token == "" {
|
||||||
return nil, fmt.Errorf("COOLIFY_TOKEN is not set")
|
return nil, cli.Err("COOLIFY_TOKEN is not set")
|
||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TriggerDeploy triggers a deployment for the specified application.
|
// TriggerDeploy triggers a deployment for the specified application.
|
||||||
func (c *CoolifyClient) TriggerDeploy(ctx context.Context, appID string, force bool) (*CoolifyDeployment, error) {
|
func (c *CoolifyClient) TriggerDeploy(ctx context.Context, appID string, force bool) (*CoolifyDeployment, error) {
|
||||||
endpoint := fmt.Sprintf("%s/api/v1/applications/%s/deploy", c.BaseURL, appID)
|
endpoint := cli.Sprintf("%s/api/v1/applications/%s/deploy", c.BaseURL, appID)
|
||||||
|
|
||||||
payload := map[string]interface{}{}
|
payload := map[string]interface{}{}
|
||||||
if force {
|
if force {
|
||||||
|
|
@ -162,19 +163,19 @@ func (c *CoolifyClient) TriggerDeploy(ctx context.Context, appID string, force b
|
||||||
|
|
||||||
body, err := json.Marshal(payload)
|
body, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
return nil, cli.WrapVerb(err, "marshal", "request")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, cli.WrapVerb(err, "create", "request")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setHeaders(req)
|
c.setHeaders(req)
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request failed: %w", err)
|
return nil, cli.Wrap(err, "request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|
@ -196,18 +197,18 @@ func (c *CoolifyClient) TriggerDeploy(ctx context.Context, appID string, force b
|
||||||
|
|
||||||
// GetDeployment retrieves a specific deployment by ID.
|
// GetDeployment retrieves a specific deployment by ID.
|
||||||
func (c *CoolifyClient) GetDeployment(ctx context.Context, appID, deploymentID string) (*CoolifyDeployment, error) {
|
func (c *CoolifyClient) GetDeployment(ctx context.Context, appID, deploymentID string) (*CoolifyDeployment, error) {
|
||||||
endpoint := fmt.Sprintf("%s/api/v1/applications/%s/deployments/%s", c.BaseURL, appID, deploymentID)
|
endpoint := cli.Sprintf("%s/api/v1/applications/%s/deployments/%s", c.BaseURL, appID, deploymentID)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, cli.WrapVerb(err, "create", "request")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setHeaders(req)
|
c.setHeaders(req)
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request failed: %w", err)
|
return nil, cli.Wrap(err, "request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|
@ -217,7 +218,7 @@ func (c *CoolifyClient) GetDeployment(ctx context.Context, appID, deploymentID s
|
||||||
|
|
||||||
var deployment CoolifyDeployment
|
var deployment CoolifyDeployment
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&deployment); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&deployment); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, cli.WrapVerb(err, "decode", "response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &deployment, nil
|
return &deployment, nil
|
||||||
|
|
@ -225,21 +226,21 @@ func (c *CoolifyClient) GetDeployment(ctx context.Context, appID, deploymentID s
|
||||||
|
|
||||||
// ListDeployments retrieves deployments for an application.
|
// ListDeployments retrieves deployments for an application.
|
||||||
func (c *CoolifyClient) ListDeployments(ctx context.Context, appID string, limit int) ([]CoolifyDeployment, error) {
|
func (c *CoolifyClient) ListDeployments(ctx context.Context, appID string, limit int) ([]CoolifyDeployment, error) {
|
||||||
endpoint := fmt.Sprintf("%s/api/v1/applications/%s/deployments", c.BaseURL, appID)
|
endpoint := cli.Sprintf("%s/api/v1/applications/%s/deployments", c.BaseURL, appID)
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
endpoint = fmt.Sprintf("%s?limit=%d", endpoint, limit)
|
endpoint = cli.Sprintf("%s?limit=%d", endpoint, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, cli.WrapVerb(err, "create", "request")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setHeaders(req)
|
c.setHeaders(req)
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request failed: %w", err)
|
return nil, cli.Wrap(err, "request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|
@ -249,7 +250,7 @@ func (c *CoolifyClient) ListDeployments(ctx context.Context, appID string, limit
|
||||||
|
|
||||||
var deployments []CoolifyDeployment
|
var deployments []CoolifyDeployment
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&deployments); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&deployments); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, cli.WrapVerb(err, "decode", "response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return deployments, nil
|
return deployments, nil
|
||||||
|
|
@ -257,7 +258,7 @@ func (c *CoolifyClient) ListDeployments(ctx context.Context, appID string, limit
|
||||||
|
|
||||||
// Rollback triggers a rollback to a previous deployment.
|
// Rollback triggers a rollback to a previous deployment.
|
||||||
func (c *CoolifyClient) Rollback(ctx context.Context, appID, deploymentID string) (*CoolifyDeployment, error) {
|
func (c *CoolifyClient) Rollback(ctx context.Context, appID, deploymentID string) (*CoolifyDeployment, error) {
|
||||||
endpoint := fmt.Sprintf("%s/api/v1/applications/%s/rollback", c.BaseURL, appID)
|
endpoint := cli.Sprintf("%s/api/v1/applications/%s/rollback", c.BaseURL, appID)
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"deployment_id": deploymentID,
|
"deployment_id": deploymentID,
|
||||||
|
|
@ -265,19 +266,19 @@ func (c *CoolifyClient) Rollback(ctx context.Context, appID, deploymentID string
|
||||||
|
|
||||||
body, err := json.Marshal(payload)
|
body, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
return nil, cli.WrapVerb(err, "marshal", "request")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, cli.WrapVerb(err, "create", "request")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setHeaders(req)
|
c.setHeaders(req)
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request failed: %w", err)
|
return nil, cli.Wrap(err, "request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|
@ -298,18 +299,18 @@ func (c *CoolifyClient) Rollback(ctx context.Context, appID, deploymentID string
|
||||||
|
|
||||||
// GetApp retrieves application details.
|
// GetApp retrieves application details.
|
||||||
func (c *CoolifyClient) GetApp(ctx context.Context, appID string) (*CoolifyApp, error) {
|
func (c *CoolifyClient) GetApp(ctx context.Context, appID string) (*CoolifyApp, error) {
|
||||||
endpoint := fmt.Sprintf("%s/api/v1/applications/%s", c.BaseURL, appID)
|
endpoint := cli.Sprintf("%s/api/v1/applications/%s", c.BaseURL, appID)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, cli.WrapVerb(err, "create", "request")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.setHeaders(req)
|
c.setHeaders(req)
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
resp, err := c.HTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("request failed: %w", err)
|
return nil, cli.Wrap(err, "request failed")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
|
@ -319,7 +320,7 @@ func (c *CoolifyClient) GetApp(ctx context.Context, appID string) (*CoolifyApp,
|
||||||
|
|
||||||
var app CoolifyApp
|
var app CoolifyApp
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&app); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&app); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, cli.WrapVerb(err, "decode", "response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &app, nil
|
return &app, nil
|
||||||
|
|
@ -343,12 +344,12 @@ func (c *CoolifyClient) parseError(resp *http.Response) error {
|
||||||
|
|
||||||
if err := json.Unmarshal(body, &errResp); err == nil {
|
if err := json.Unmarshal(body, &errResp); err == nil {
|
||||||
if errResp.Message != "" {
|
if errResp.Message != "" {
|
||||||
return fmt.Errorf("API error (%d): %s", resp.StatusCode, errResp.Message)
|
return cli.Err("API error (%d): %s", resp.StatusCode, errResp.Message)
|
||||||
}
|
}
|
||||||
if errResp.Error != "" {
|
if errResp.Error != "" {
|
||||||
return fmt.Errorf("API error (%d): %s", resp.StatusCode, errResp.Error)
|
return cli.Err("API error (%d): %s", resp.StatusCode, errResp.Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("API error (%d): %s", resp.StatusCode, string(body))
|
return cli.Err("API error (%d): %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Environment represents a deployment environment.
|
// Environment represents a deployment environment.
|
||||||
|
|
@ -120,13 +121,13 @@ func Deploy(ctx context.Context, opts DeployOptions) (*DeploymentStatus, error)
|
||||||
// Load config
|
// Load config
|
||||||
config, err := LoadCoolifyConfig(opts.Dir)
|
config, err := LoadCoolifyConfig(opts.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load Coolify config: %w", err)
|
return nil, cli.WrapVerb(err, "load", "Coolify config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get app ID for environment
|
// Get app ID for environment
|
||||||
appID := getAppIDForEnvironment(config, opts.Environment)
|
appID := getAppIDForEnvironment(config, opts.Environment)
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
return nil, fmt.Errorf("no app ID configured for %s environment", opts.Environment)
|
return nil, cli.Err("no app ID configured for %s environment", opts.Environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
|
|
@ -135,7 +136,7 @@ func Deploy(ctx context.Context, opts DeployOptions) (*DeploymentStatus, error)
|
||||||
// Trigger deployment
|
// Trigger deployment
|
||||||
deployment, err := client.TriggerDeploy(ctx, appID, opts.Force)
|
deployment, err := client.TriggerDeploy(ctx, appID, opts.Force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to trigger deployment: %w", err)
|
return nil, cli.WrapVerb(err, "trigger", "deployment")
|
||||||
}
|
}
|
||||||
|
|
||||||
status := convertDeployment(deployment)
|
status := convertDeployment(deployment)
|
||||||
|
|
@ -169,13 +170,13 @@ func DeployStatus(ctx context.Context, opts StatusOptions) (*DeploymentStatus, e
|
||||||
// Load config
|
// Load config
|
||||||
config, err := LoadCoolifyConfig(opts.Dir)
|
config, err := LoadCoolifyConfig(opts.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load Coolify config: %w", err)
|
return nil, cli.WrapVerb(err, "load", "Coolify config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get app ID for environment
|
// Get app ID for environment
|
||||||
appID := getAppIDForEnvironment(config, opts.Environment)
|
appID := getAppIDForEnvironment(config, opts.Environment)
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
return nil, fmt.Errorf("no app ID configured for %s environment", opts.Environment)
|
return nil, cli.Err("no app ID configured for %s environment", opts.Environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
|
|
@ -187,16 +188,16 @@ func DeployStatus(ctx context.Context, opts StatusOptions) (*DeploymentStatus, e
|
||||||
// Get specific deployment
|
// Get specific deployment
|
||||||
deployment, err = client.GetDeployment(ctx, appID, opts.DeploymentID)
|
deployment, err = client.GetDeployment(ctx, appID, opts.DeploymentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get deployment: %w", err)
|
return nil, cli.WrapVerb(err, "get", "deployment")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Get latest deployment
|
// Get latest deployment
|
||||||
deployments, err := client.ListDeployments(ctx, appID, 1)
|
deployments, err := client.ListDeployments(ctx, appID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list deployments: %w", err)
|
return nil, cli.WrapVerb(err, "list", "deployments")
|
||||||
}
|
}
|
||||||
if len(deployments) == 0 {
|
if len(deployments) == 0 {
|
||||||
return nil, fmt.Errorf("no deployments found")
|
return nil, cli.Err("no deployments found")
|
||||||
}
|
}
|
||||||
deployment = &deployments[0]
|
deployment = &deployments[0]
|
||||||
}
|
}
|
||||||
|
|
@ -227,13 +228,13 @@ func Rollback(ctx context.Context, opts RollbackOptions) (*DeploymentStatus, err
|
||||||
// Load config
|
// Load config
|
||||||
config, err := LoadCoolifyConfig(opts.Dir)
|
config, err := LoadCoolifyConfig(opts.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load Coolify config: %w", err)
|
return nil, cli.WrapVerb(err, "load", "Coolify config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get app ID for environment
|
// Get app ID for environment
|
||||||
appID := getAppIDForEnvironment(config, opts.Environment)
|
appID := getAppIDForEnvironment(config, opts.Environment)
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
return nil, fmt.Errorf("no app ID configured for %s environment", opts.Environment)
|
return nil, cli.Err("no app ID configured for %s environment", opts.Environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
|
|
@ -245,7 +246,7 @@ func Rollback(ctx context.Context, opts RollbackOptions) (*DeploymentStatus, err
|
||||||
// Find previous successful deployment
|
// Find previous successful deployment
|
||||||
deployments, err := client.ListDeployments(ctx, appID, 10)
|
deployments, err := client.ListDeployments(ctx, appID, 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list deployments: %w", err)
|
return nil, cli.WrapVerb(err, "list", "deployments")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the first (current) deployment, find the last successful one
|
// Skip the first (current) deployment, find the last successful one
|
||||||
|
|
@ -260,14 +261,14 @@ func Rollback(ctx context.Context, opts RollbackOptions) (*DeploymentStatus, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if deploymentID == "" {
|
if deploymentID == "" {
|
||||||
return nil, fmt.Errorf("no previous successful deployment found to rollback to")
|
return nil, cli.Err("no previous successful deployment found to rollback to")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger rollback
|
// Trigger rollback
|
||||||
deployment, err := client.Rollback(ctx, appID, deploymentID)
|
deployment, err := client.Rollback(ctx, appID, deploymentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to trigger rollback: %w", err)
|
return nil, cli.WrapVerb(err, "trigger", "rollback")
|
||||||
}
|
}
|
||||||
|
|
||||||
status := convertDeployment(deployment)
|
status := convertDeployment(deployment)
|
||||||
|
|
@ -298,13 +299,13 @@ func ListDeployments(ctx context.Context, dir string, env Environment, limit int
|
||||||
// Load config
|
// Load config
|
||||||
config, err := LoadCoolifyConfig(dir)
|
config, err := LoadCoolifyConfig(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load Coolify config: %w", err)
|
return nil, cli.WrapVerb(err, "load", "Coolify config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get app ID for environment
|
// Get app ID for environment
|
||||||
appID := getAppIDForEnvironment(config, env)
|
appID := getAppIDForEnvironment(config, env)
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
return nil, fmt.Errorf("no app ID configured for %s environment", env)
|
return nil, cli.Err("no app ID configured for %s environment", env)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create client
|
// Create client
|
||||||
|
|
@ -312,7 +313,7 @@ func ListDeployments(ctx context.Context, dir string, env Environment, limit int
|
||||||
|
|
||||||
deployments, err := client.ListDeployments(ctx, appID, limit)
|
deployments, err := client.ListDeployments(ctx, appID, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list deployments: %w", err)
|
return nil, cli.WrapVerb(err, "list", "deployments")
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]DeploymentStatus, len(deployments))
|
result := make([]DeploymentStatus, len(deployments))
|
||||||
|
|
@ -364,7 +365,7 @@ func waitForDeployment(ctx context.Context, client *CoolifyClient, appID, deploy
|
||||||
|
|
||||||
deployment, err := client.GetDeployment(ctx, appID, deploymentID)
|
deployment, err := client.GetDeployment(ctx, appID, deploymentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get deployment status: %w", err)
|
return nil, cli.WrapVerb(err, "get", "deployment status")
|
||||||
}
|
}
|
||||||
|
|
||||||
status := convertDeployment(deployment)
|
status := convertDeployment(deployment)
|
||||||
|
|
@ -374,9 +375,9 @@ func waitForDeployment(ctx context.Context, client *CoolifyClient, appID, deploy
|
||||||
case "finished", "success":
|
case "finished", "success":
|
||||||
return status, nil
|
return status, nil
|
||||||
case "failed", "error":
|
case "failed", "error":
|
||||||
return status, fmt.Errorf("deployment failed: %s", deployment.Status)
|
return status, cli.Err("deployment failed: %s", deployment.Status)
|
||||||
case "cancelled":
|
case "cancelled":
|
||||||
return status, fmt.Errorf("deployment was cancelled")
|
return status, cli.Err("deployment was cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Still in progress, wait and retry
|
// Still in progress, wait and retry
|
||||||
|
|
@ -387,7 +388,7 @@ func waitForDeployment(ctx context.Context, client *CoolifyClient, appID, deploy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("deployment timed out after %v", timeout)
|
return nil, cli.Err("deployment timed out after %v", timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDeploymentComplete returns true if the status indicates completion.
|
// IsDeploymentComplete returns true if the status indicates completion.
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DockerfileConfig holds configuration for generating a Dockerfile.
|
// DockerfileConfig holds configuration for generating a Dockerfile.
|
||||||
|
|
@ -59,12 +60,12 @@ func DetectDockerfileConfig(dir string) (*DockerfileConfig, error) {
|
||||||
composerPath := filepath.Join(dir, "composer.json")
|
composerPath := filepath.Join(dir, "composer.json")
|
||||||
composerData, err := os.ReadFile(composerPath)
|
composerData, err := os.ReadFile(composerPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read composer.json: %w", err)
|
return nil, cli.WrapVerb(err, "read", "composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
var composer ComposerJSON
|
var composer ComposerJSON
|
||||||
if err := json.Unmarshal(composerData, &composer); err != nil {
|
if err := json.Unmarshal(composerData, &composer); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse composer.json: %w", err)
|
return nil, cli.WrapVerb(err, "parse", "composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect PHP version from composer.json
|
// Detect PHP version from composer.json
|
||||||
|
|
@ -99,13 +100,13 @@ func GenerateDockerfileFromConfig(config *DockerfileConfig) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
// Base image
|
// Base image
|
||||||
baseTag := fmt.Sprintf("latest-php%s", config.PHPVersion)
|
baseTag := cli.Sprintf("latest-php%s", config.PHPVersion)
|
||||||
if config.UseAlpine {
|
if config.UseAlpine {
|
||||||
baseTag += "-alpine"
|
baseTag += "-alpine"
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf("# Auto-generated Dockerfile for FrankenPHP\n"))
|
sb.WriteString("# Auto-generated Dockerfile for FrankenPHP\n")
|
||||||
sb.WriteString(fmt.Sprintf("# Generated by Core Framework\n\n"))
|
sb.WriteString("# Generated by Core Framework\n\n")
|
||||||
|
|
||||||
// Multi-stage build for smaller images
|
// Multi-stage build for smaller images
|
||||||
if config.HasAssets {
|
if config.HasAssets {
|
||||||
|
|
@ -150,16 +151,16 @@ func GenerateDockerfileFromConfig(config *DockerfileConfig) string {
|
||||||
// PHP build stage
|
// PHP build stage
|
||||||
stageNum := 2
|
stageNum := 2
|
||||||
if config.HasAssets {
|
if config.HasAssets {
|
||||||
sb.WriteString(fmt.Sprintf("# Stage %d: PHP application\n", stageNum))
|
sb.WriteString(cli.Sprintf("# Stage %d: PHP application\n", stageNum))
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf("FROM %s:%s AS app\n\n", config.BaseImage, baseTag))
|
sb.WriteString(cli.Sprintf("FROM %s:%s AS app\n\n", config.BaseImage, baseTag))
|
||||||
|
|
||||||
sb.WriteString("WORKDIR /app\n\n")
|
sb.WriteString("WORKDIR /app\n\n")
|
||||||
|
|
||||||
// Install PHP extensions if needed
|
// Install PHP extensions if needed
|
||||||
if len(config.PHPExtensions) > 0 {
|
if len(config.PHPExtensions) > 0 {
|
||||||
sb.WriteString("# Install PHP extensions\n")
|
sb.WriteString("# Install PHP extensions\n")
|
||||||
sb.WriteString(fmt.Sprintf("RUN install-php-extensions %s\n\n", strings.Join(config.PHPExtensions, " ")))
|
sb.WriteString(cli.Sprintf("RUN install-php-extensions %s\n\n", strings.Join(config.PHPExtensions, " ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy composer files first for better caching
|
// Copy composer files first for better caching
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinkedPackage represents a linked local package.
|
// LinkedPackage represents a linked local package.
|
||||||
|
|
@ -27,12 +28,12 @@ func readComposerJSON(dir string) (map[string]json.RawMessage, error) {
|
||||||
composerPath := filepath.Join(dir, "composer.json")
|
composerPath := filepath.Join(dir, "composer.json")
|
||||||
data, err := os.ReadFile(composerPath)
|
data, err := os.ReadFile(composerPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read composer.json: %w", err)
|
return nil, cli.WrapVerb(err, "read", "composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
var raw map[string]json.RawMessage
|
var raw map[string]json.RawMessage
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
if err := json.Unmarshal(data, &raw); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse composer.json: %w", err)
|
return nil, cli.WrapVerb(err, "parse", "composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
return raw, nil
|
return raw, nil
|
||||||
|
|
@ -44,14 +45,14 @@ func writeComposerJSON(dir string, raw map[string]json.RawMessage) error {
|
||||||
|
|
||||||
data, err := json.MarshalIndent(raw, "", " ")
|
data, err := json.MarshalIndent(raw, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal composer.json: %w", err)
|
return cli.WrapVerb(err, "marshal", "composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add trailing newline
|
// Add trailing newline
|
||||||
data = append(data, '\n')
|
data = append(data, '\n')
|
||||||
|
|
||||||
if err := os.WriteFile(composerPath, data, 0644); err != nil {
|
if err := os.WriteFile(composerPath, data, 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write composer.json: %w", err)
|
return cli.WrapVerb(err, "write", "composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -66,7 +67,7 @@ func getRepositories(raw map[string]json.RawMessage) ([]composerRepository, erro
|
||||||
|
|
||||||
var repos []composerRepository
|
var repos []composerRepository
|
||||||
if err := json.Unmarshal(reposRaw, &repos); err != nil {
|
if err := json.Unmarshal(reposRaw, &repos); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse repositories: %w", err)
|
return nil, cli.WrapVerb(err, "parse", "repositories")
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, nil
|
return repos, nil
|
||||||
|
|
@ -81,7 +82,7 @@ func setRepositories(raw map[string]json.RawMessage, repos []composerRepository)
|
||||||
|
|
||||||
reposData, err := json.Marshal(repos)
|
reposData, err := json.Marshal(repos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal repositories: %w", err)
|
return cli.WrapVerb(err, "marshal", "repositories")
|
||||||
}
|
}
|
||||||
|
|
||||||
raw["repositories"] = reposData
|
raw["repositories"] = reposData
|
||||||
|
|
@ -93,7 +94,7 @@ func getPackageInfo(packagePath string) (name, version string, err error) {
|
||||||
composerPath := filepath.Join(packagePath, "composer.json")
|
composerPath := filepath.Join(packagePath, "composer.json")
|
||||||
data, err := os.ReadFile(composerPath)
|
data, err := os.ReadFile(composerPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("failed to read package composer.json: %w", err)
|
return "", "", cli.WrapVerb(err, "read", "package composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
var pkg struct {
|
var pkg struct {
|
||||||
|
|
@ -102,11 +103,11 @@ func getPackageInfo(packagePath string) (name, version string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &pkg); err != nil {
|
if err := json.Unmarshal(data, &pkg); err != nil {
|
||||||
return "", "", fmt.Errorf("failed to parse package composer.json: %w", err)
|
return "", "", cli.WrapVerb(err, "parse", "package composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == "" {
|
if pkg.Name == "" {
|
||||||
return "", "", fmt.Errorf("package name not found in composer.json")
|
return "", "", cli.Err("package name not found in composer.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkg.Name, pkg.Version, nil
|
return pkg.Name, pkg.Version, nil
|
||||||
|
|
@ -115,7 +116,7 @@ func getPackageInfo(packagePath string) (name, version string, err error) {
|
||||||
// LinkPackages adds path repositories to composer.json for local package development.
|
// LinkPackages adds path repositories to composer.json for local package development.
|
||||||
func LinkPackages(dir string, packages []string) error {
|
func LinkPackages(dir string, packages []string) error {
|
||||||
if !IsPHPProject(dir) {
|
if !IsPHPProject(dir) {
|
||||||
return fmt.Errorf("not a PHP project (missing composer.json)")
|
return cli.Err("not a PHP project (missing composer.json)")
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, err := readComposerJSON(dir)
|
raw, err := readComposerJSON(dir)
|
||||||
|
|
@ -132,18 +133,18 @@ func LinkPackages(dir string, packages []string) error {
|
||||||
// Resolve absolute path
|
// Resolve absolute path
|
||||||
absPath, err := filepath.Abs(packagePath)
|
absPath, err := filepath.Abs(packagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to resolve path %s: %w", packagePath, err)
|
return cli.Err("failed to resolve path %s: %w", packagePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the path exists and has a composer.json
|
// Verify the path exists and has a composer.json
|
||||||
if !IsPHPProject(absPath) {
|
if !IsPHPProject(absPath) {
|
||||||
return fmt.Errorf("not a PHP package (missing composer.json): %s", absPath)
|
return cli.Err("not a PHP package (missing composer.json): %s", absPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get package name for validation
|
// Get package name for validation
|
||||||
pkgName, _, err := getPackageInfo(absPath)
|
pkgName, _, err := getPackageInfo(absPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get package info from %s: %w", absPath, err)
|
return cli.Err("failed to get package info from %s: %w", absPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already linked
|
// Check if already linked
|
||||||
|
|
@ -168,7 +169,7 @@ func LinkPackages(dir string, packages []string) error {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
fmt.Printf("Linked: %s -> %s\n", pkgName, absPath)
|
cli.Print("Linked: %s -> %s\n", pkgName, absPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setRepositories(raw, repos); err != nil {
|
if err := setRepositories(raw, repos); err != nil {
|
||||||
|
|
@ -181,7 +182,7 @@ func LinkPackages(dir string, packages []string) error {
|
||||||
// UnlinkPackages removes path repositories from composer.json.
|
// UnlinkPackages removes path repositories from composer.json.
|
||||||
func UnlinkPackages(dir string, packages []string) error {
|
func UnlinkPackages(dir string, packages []string) error {
|
||||||
if !IsPHPProject(dir) {
|
if !IsPHPProject(dir) {
|
||||||
return fmt.Errorf("not a PHP project (missing composer.json)")
|
return cli.Err("not a PHP project (missing composer.json)")
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, err := readComposerJSON(dir)
|
raw, err := readComposerJSON(dir)
|
||||||
|
|
@ -216,7 +217,7 @@ func UnlinkPackages(dir string, packages []string) error {
|
||||||
pkgName, _, err := getPackageInfo(repo.URL)
|
pkgName, _, err := getPackageInfo(repo.URL)
|
||||||
if err == nil && toUnlink[pkgName] {
|
if err == nil && toUnlink[pkgName] {
|
||||||
shouldUnlink = true
|
shouldUnlink = true
|
||||||
fmt.Printf("Unlinked: %s\n", pkgName)
|
cli.Print("Unlinked: %s\n", pkgName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,7 +225,7 @@ func UnlinkPackages(dir string, packages []string) error {
|
||||||
for pkg := range toUnlink {
|
for pkg := range toUnlink {
|
||||||
if repo.URL == pkg || filepath.Base(repo.URL) == pkg {
|
if repo.URL == pkg || filepath.Base(repo.URL) == pkg {
|
||||||
shouldUnlink = true
|
shouldUnlink = true
|
||||||
fmt.Printf("Unlinked: %s\n", repo.URL)
|
cli.Print("Unlinked: %s\n", repo.URL)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +245,7 @@ func UnlinkPackages(dir string, packages []string) error {
|
||||||
// UpdatePackages runs composer update for specific packages.
|
// UpdatePackages runs composer update for specific packages.
|
||||||
func UpdatePackages(dir string, packages []string) error {
|
func UpdatePackages(dir string, packages []string) error {
|
||||||
if !IsPHPProject(dir) {
|
if !IsPHPProject(dir) {
|
||||||
return fmt.Errorf("not a PHP project (missing composer.json)")
|
return cli.Err("not a PHP project (missing composer.json)")
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"update"}
|
args := []string{"update"}
|
||||||
|
|
@ -261,7 +262,7 @@ func UpdatePackages(dir string, packages []string) error {
|
||||||
// ListLinkedPackages returns all path repositories from composer.json.
|
// ListLinkedPackages returns all path repositories from composer.json.
|
||||||
func ListLinkedPackages(dir string) ([]LinkedPackage, error) {
|
func ListLinkedPackages(dir string) ([]LinkedPackage, error) {
|
||||||
if !IsPHPProject(dir) {
|
if !IsPHPProject(dir) {
|
||||||
return nil, fmt.Errorf("not a PHP project (missing composer.json)")
|
return nil, cli.Err("not a PHP project (missing composer.json)")
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, err := readComposerJSON(dir)
|
raw, err := readComposerJSON(dir)
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options configures the development server.
|
// Options configures the development server.
|
||||||
|
|
@ -69,7 +70,7 @@ func (d *DevServer) Start(ctx context.Context, opts Options) error {
|
||||||
defer d.mu.Unlock()
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
if d.running {
|
if d.running {
|
||||||
return fmt.Errorf("dev server is already running")
|
return cli.Err("dev server is already running")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge options
|
// Merge options
|
||||||
|
|
@ -79,14 +80,14 @@ func (d *DevServer) Start(ctx context.Context, opts Options) error {
|
||||||
if d.opts.Dir == "" {
|
if d.opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
d.opts.Dir = cwd
|
d.opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify this is a Laravel project
|
// Verify this is a Laravel project
|
||||||
if !IsLaravelProject(d.opts.Dir) {
|
if !IsLaravelProject(d.opts.Dir) {
|
||||||
return fmt.Errorf("not a Laravel project: %s", d.opts.Dir)
|
return cli.Err("not a Laravel project: %s", d.opts.Dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create cancellable context
|
// Create cancellable context
|
||||||
|
|
@ -119,7 +120,7 @@ func (d *DevServer) Start(ctx context.Context, opts Options) error {
|
||||||
var err error
|
var err error
|
||||||
certFile, keyFile, err = SetupSSLIfNeeded(domain, SSLOptions{})
|
certFile, keyFile, err = SetupSSLIfNeeded(domain, SSLOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to setup SSL: %w", err)
|
return cli.WrapVerb(err, "setup", "SSL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +188,7 @@ func (d *DevServer) Start(ctx context.Context, opts Options) error {
|
||||||
var startErrors []error
|
var startErrors []error
|
||||||
for _, svc := range d.services {
|
for _, svc := range d.services {
|
||||||
if err := svc.Start(d.ctx); err != nil {
|
if err := svc.Start(d.ctx); err != nil {
|
||||||
startErrors = append(startErrors, fmt.Errorf("%s: %w", svc.Name(), err))
|
startErrors = append(startErrors, cli.Err("%s: %v", svc.Name(), err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,7 +197,7 @@ func (d *DevServer) Start(ctx context.Context, opts Options) error {
|
||||||
for _, svc := range d.services {
|
for _, svc := range d.services {
|
||||||
svc.Stop()
|
svc.Stop()
|
||||||
}
|
}
|
||||||
return fmt.Errorf("failed to start services: %v", startErrors)
|
return cli.Err("failed to start services: %v", startErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.running = true
|
d.running = true
|
||||||
|
|
@ -252,14 +253,14 @@ func (d *DevServer) Stop() error {
|
||||||
for i := len(d.services) - 1; i >= 0; i-- {
|
for i := len(d.services) - 1; i >= 0; i-- {
|
||||||
svc := d.services[i]
|
svc := d.services[i]
|
||||||
if err := svc.Stop(); err != nil {
|
if err := svc.Stop(); err != nil {
|
||||||
stopErrors = append(stopErrors, fmt.Errorf("%s: %w", svc.Name(), err))
|
stopErrors = append(stopErrors, cli.Err("%s: %v", svc.Name(), err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.running = false
|
d.running = false
|
||||||
|
|
||||||
if len(stopErrors) > 0 {
|
if len(stopErrors) > 0 {
|
||||||
return fmt.Errorf("errors stopping services: %v", stopErrors)
|
return cli.Err("errors stopping services: %v", stopErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -283,7 +284,7 @@ func (d *DevServer) Logs(service string, follow bool) (io.ReadCloser, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("service not found: %s", service)
|
return nil, cli.Err("service not found: %s", service)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unifiedLogs creates a reader that combines logs from all services.
|
// unifiedLogs creates a reader that combines logs from all services.
|
||||||
|
|
@ -297,7 +298,7 @@ func (d *DevServer) unifiedLogs(follow bool) (io.ReadCloser, error) {
|
||||||
for _, r := range readers {
|
for _, r := range readers {
|
||||||
r.Close()
|
r.Close()
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to get logs for %s: %w", svc.Name(), err)
|
return nil, cli.Err("failed to get logs for %s: %v", svc.Name(), err)
|
||||||
}
|
}
|
||||||
readers = append(readers, reader)
|
readers = append(readers, reader)
|
||||||
}
|
}
|
||||||
|
|
@ -363,7 +364,7 @@ func (m *multiServiceReader) Read(p []byte) (n int, err error) {
|
||||||
n, err := reader.Read(buf)
|
n, err := reader.Read(buf)
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
// Prefix with service name
|
// Prefix with service name
|
||||||
prefix := fmt.Sprintf("[%s] ", m.services[i].Name())
|
prefix := cli.Sprintf("[%s] ", m.services[i].Name())
|
||||||
copy(p, prefix)
|
copy(p, prefix)
|
||||||
copy(p[len(prefix):], buf[:n])
|
copy(p[len(prefix):], buf[:n])
|
||||||
return n + len(prefix), nil
|
return n + len(prefix), nil
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ package php
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatOptions configures PHP code formatting.
|
// FormatOptions configures PHP code formatting.
|
||||||
|
|
@ -122,7 +123,7 @@ func Format(ctx context.Context, opts FormatOptions) error {
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +135,7 @@ func Format(ctx context.Context, opts FormatOptions) error {
|
||||||
// Check if formatter is available
|
// Check if formatter is available
|
||||||
formatter, found := DetectFormatter(opts.Dir)
|
formatter, found := DetectFormatter(opts.Dir)
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("no formatter found (install Laravel Pint: composer require laravel/pint --dev)")
|
return cli.Err("no formatter found (install Laravel Pint: composer require laravel/pint --dev)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdName string
|
var cmdName string
|
||||||
|
|
@ -158,7 +159,7 @@ func Analyse(ctx context.Context, opts AnalyseOptions) error {
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +171,7 @@ func Analyse(ctx context.Context, opts AnalyseOptions) error {
|
||||||
// Check if analyser is available
|
// Check if analyser is available
|
||||||
analyser, found := DetectAnalyser(opts.Dir)
|
analyser, found := DetectAnalyser(opts.Dir)
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)")
|
return cli.Err("no static analyser found (install PHPStan: composer require phpstan/phpstan --dev)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmdName string
|
var cmdName string
|
||||||
|
|
@ -226,7 +227,7 @@ func buildPHPStanCommand(opts AnalyseOptions) (string, []string) {
|
||||||
args := []string{"analyse"}
|
args := []string{"analyse"}
|
||||||
|
|
||||||
if opts.Level > 0 {
|
if opts.Level > 0 {
|
||||||
args = append(args, "--level", fmt.Sprintf("%d", opts.Level))
|
args = append(args, "--level", cli.Sprintf("%d", opts.Level))
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Memory != "" {
|
if opts.Memory != "" {
|
||||||
|
|
@ -292,7 +293,7 @@ func RunPsalm(ctx context.Context, opts PsalmOptions) error {
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
@ -311,7 +312,7 @@ func RunPsalm(ctx context.Context, opts PsalmOptions) error {
|
||||||
args := []string{"--no-progress"}
|
args := []string{"--no-progress"}
|
||||||
|
|
||||||
if opts.Level > 0 && opts.Level <= 8 {
|
if opts.Level > 0 && opts.Level <= 8 {
|
||||||
args = append(args, fmt.Sprintf("--error-level=%d", opts.Level))
|
args = append(args, cli.Sprintf("--error-level=%d", opts.Level))
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Fix {
|
if opts.Fix {
|
||||||
|
|
@ -368,7 +369,7 @@ func RunAudit(ctx context.Context, opts AuditOptions) ([]AuditResult, error) {
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get working directory: %w", err)
|
return nil, cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
@ -520,7 +521,7 @@ func RunRector(ctx context.Context, opts RectorOptions) error {
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
@ -597,7 +598,7 @@ func RunInfection(ctx context.Context, opts InfectionOptions) error {
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
@ -629,9 +630,9 @@ func RunInfection(ctx context.Context, opts InfectionOptions) error {
|
||||||
threads = 4
|
threads = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, fmt.Sprintf("--min-msi=%d", minMSI))
|
args = append(args, cli.Sprintf("--min-msi=%d", minMSI))
|
||||||
args = append(args, fmt.Sprintf("--min-covered-msi=%d", minCoveredMSI))
|
args = append(args, cli.Sprintf("--min-covered-msi=%d", minCoveredMSI))
|
||||||
args = append(args, fmt.Sprintf("--threads=%d", threads))
|
args = append(args, cli.Sprintf("--threads=%d", threads))
|
||||||
|
|
||||||
if opts.Filter != "" {
|
if opts.Filter != "" {
|
||||||
args = append(args, "--filter="+opts.Filter)
|
args = append(args, "--filter="+opts.Filter)
|
||||||
|
|
@ -774,7 +775,7 @@ func RunSecurityChecks(ctx context.Context, opts SecurityOptions) (*SecurityResu
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get working directory: %w", err)
|
return nil, cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
@ -793,7 +794,7 @@ func RunSecurityChecks(ctx context.Context, opts SecurityOptions) (*SecurityResu
|
||||||
CWE: "CWE-1395",
|
CWE: "CWE-1395",
|
||||||
}
|
}
|
||||||
if !check.Passed {
|
if !check.Passed {
|
||||||
check.Message = fmt.Sprintf("Found %d vulnerabilities", audit.Vulnerabilities)
|
check.Message = cli.Sprintf("Found %d vulnerabilities", audit.Vulnerabilities)
|
||||||
}
|
}
|
||||||
result.Checks = append(result.Checks, check)
|
result.Checks = append(result.Checks, check)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ package php
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
@ -13,6 +12,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service represents a managed development service.
|
// Service represents a managed development service.
|
||||||
|
|
@ -75,12 +76,12 @@ func (s *baseService) Status() ServiceStatus {
|
||||||
|
|
||||||
func (s *baseService) Logs(follow bool) (io.ReadCloser, error) {
|
func (s *baseService) Logs(follow bool) (io.ReadCloser, error) {
|
||||||
if s.logPath == "" {
|
if s.logPath == "" {
|
||||||
return nil, fmt.Errorf("no log file available for %s", s.name)
|
return nil, cli.Err("no log file available for %s", s.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(s.logPath)
|
file, err := os.Open(s.logPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
return nil, cli.WrapVerb(err, "open", "log file")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !follow {
|
if !follow {
|
||||||
|
|
@ -96,19 +97,19 @@ func (s *baseService) startProcess(ctx context.Context, cmdName string, args []s
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
if s.running {
|
if s.running {
|
||||||
return fmt.Errorf("%s is already running", s.name)
|
return cli.Err("%s is already running", s.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create log file
|
// Create log file
|
||||||
logDir := filepath.Join(s.dir, ".core", "logs")
|
logDir := filepath.Join(s.dir, ".core", "logs")
|
||||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create log directory: %w", err)
|
return cli.WrapVerb(err, "create", "log directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logPath = filepath.Join(logDir, fmt.Sprintf("%s.log", strings.ToLower(s.name)))
|
s.logPath = filepath.Join(logDir, cli.Sprintf("%s.log", strings.ToLower(s.name)))
|
||||||
logFile, err := os.OpenFile(s.logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
logFile, err := os.OpenFile(s.logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create log file: %w", err)
|
return cli.WrapVerb(err, "create", "log file")
|
||||||
}
|
}
|
||||||
s.logFile = logFile
|
s.logFile = logFile
|
||||||
|
|
||||||
|
|
@ -127,7 +128,7 @@ func (s *baseService) startProcess(ctx context.Context, cmdName string, args []s
|
||||||
if err := s.cmd.Start(); err != nil {
|
if err := s.cmd.Start(); err != nil {
|
||||||
logFile.Close()
|
logFile.Close()
|
||||||
s.lastError = err
|
s.lastError = err
|
||||||
return fmt.Errorf("failed to start %s: %w", s.name, err)
|
return cli.WrapVerb(err, "start", s.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.running = true
|
s.running = true
|
||||||
|
|
@ -235,15 +236,15 @@ func (s *FrankenPHPService) Start(ctx context.Context) error {
|
||||||
args := []string{
|
args := []string{
|
||||||
"artisan", "octane:start",
|
"artisan", "octane:start",
|
||||||
"--server=frankenphp",
|
"--server=frankenphp",
|
||||||
fmt.Sprintf("--port=%d", s.port),
|
cli.Sprintf("--port=%d", s.port),
|
||||||
"--no-interaction",
|
"--no-interaction",
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.https && s.certFile != "" && s.keyFile != "" {
|
if s.https && s.certFile != "" && s.keyFile != "" {
|
||||||
args = append(args,
|
args = append(args,
|
||||||
fmt.Sprintf("--https-port=%d", s.httpsPort),
|
cli.Sprintf("--https-port=%d", s.httpsPort),
|
||||||
fmt.Sprintf("--https-certificate=%s", s.certFile),
|
cli.Sprintf("--https-certificate=%s", s.certFile),
|
||||||
fmt.Sprintf("--https-certificate-key=%s", s.keyFile),
|
cli.Sprintf("--https-certificate-key=%s", s.keyFile),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +373,7 @@ type ReverbOptions struct {
|
||||||
func (s *ReverbService) Start(ctx context.Context) error {
|
func (s *ReverbService) Start(ctx context.Context) error {
|
||||||
args := []string{
|
args := []string{
|
||||||
"artisan", "reverb:start",
|
"artisan", "reverb:start",
|
||||||
fmt.Sprintf("--port=%d", s.port),
|
cli.Sprintf("--port=%d", s.port),
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.startProcess(ctx, "php", args, nil)
|
return s.startProcess(ctx, "php", args, nil)
|
||||||
|
|
@ -413,13 +414,13 @@ type RedisOptions struct {
|
||||||
|
|
||||||
func (s *RedisService) Start(ctx context.Context) error {
|
func (s *RedisService) Start(ctx context.Context) error {
|
||||||
args := []string{
|
args := []string{
|
||||||
"--port", fmt.Sprintf("%d", s.port),
|
"--port", cli.Sprintf("%d", s.port),
|
||||||
"--daemonize", "no",
|
"--daemonize", "no",
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.configFile != "" {
|
if s.configFile != "" {
|
||||||
args = []string{s.configFile}
|
args = []string{s.configFile}
|
||||||
args = append(args, "--port", fmt.Sprintf("%d", s.port), "--daemonize", "no")
|
args = append(args, "--port", cli.Sprintf("%d", s.port), "--daemonize", "no")
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.startProcess(ctx, "redis-server", args, nil)
|
return s.startProcess(ctx, "redis-server", args, nil)
|
||||||
|
|
@ -427,7 +428,7 @@ func (s *RedisService) Start(ctx context.Context) error {
|
||||||
|
|
||||||
func (s *RedisService) Stop() error {
|
func (s *RedisService) Stop() error {
|
||||||
// Try graceful shutdown via redis-cli
|
// Try graceful shutdown via redis-cli
|
||||||
cmd := exec.Command("redis-cli", "-p", fmt.Sprintf("%d", s.port), "shutdown", "nosave")
|
cmd := exec.Command("redis-cli", "-p", cli.Sprintf("%d", s.port), "shutdown", "nosave")
|
||||||
cmd.Run() // Ignore errors
|
cmd.Run() // Ignore errors
|
||||||
|
|
||||||
return s.stopProcess()
|
return s.stopProcess()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package php
|
package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -25,13 +26,13 @@ func GetSSLDir(opts SSLOptions) (string, error) {
|
||||||
if dir == "" {
|
if dir == "" {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get home directory: %w", err)
|
return "", cli.WrapVerb(err, "get", "home directory")
|
||||||
}
|
}
|
||||||
dir = filepath.Join(home, DefaultSSLDir)
|
dir = filepath.Join(home, DefaultSSLDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
return "", fmt.Errorf("failed to create SSL directory: %w", err)
|
return "", cli.WrapVerb(err, "create", "SSL directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
return dir, nil
|
return dir, nil
|
||||||
|
|
@ -44,8 +45,8 @@ func CertPaths(domain string, opts SSLOptions) (certFile, keyFile string, err er
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
certFile = filepath.Join(dir, fmt.Sprintf("%s.pem", domain))
|
certFile = filepath.Join(dir, cli.Sprintf("%s.pem", domain))
|
||||||
keyFile = filepath.Join(dir, fmt.Sprintf("%s-key.pem", domain))
|
keyFile = filepath.Join(dir, cli.Sprintf("%s-key.pem", domain))
|
||||||
|
|
||||||
return certFile, keyFile, nil
|
return certFile, keyFile, nil
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +75,7 @@ func CertsExist(domain string, opts SSLOptions) bool {
|
||||||
func SetupSSL(domain string, opts SSLOptions) error {
|
func SetupSSL(domain string, opts SSLOptions) error {
|
||||||
// Check if mkcert is installed
|
// Check if mkcert is installed
|
||||||
if _, err := exec.LookPath("mkcert"); err != nil {
|
if _, err := exec.LookPath("mkcert"); err != nil {
|
||||||
return fmt.Errorf("mkcert is not installed. Install it with: brew install mkcert (macOS) or see https://github.com/FiloSottile/mkcert")
|
return cli.Err("mkcert is not installed. Install it with: brew install mkcert (macOS) or see https://github.com/FiloSottile/mkcert")
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := GetSSLDir(opts)
|
dir, err := GetSSLDir(opts)
|
||||||
|
|
@ -85,12 +86,12 @@ func SetupSSL(domain string, opts SSLOptions) error {
|
||||||
// Install local CA (idempotent operation)
|
// Install local CA (idempotent operation)
|
||||||
installCmd := exec.Command("mkcert", "-install")
|
installCmd := exec.Command("mkcert", "-install")
|
||||||
if output, err := installCmd.CombinedOutput(); err != nil {
|
if output, err := installCmd.CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("failed to install mkcert CA: %w\n%s", err, output)
|
return cli.Err("failed to install mkcert CA: %v\n%s", err, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate certificates
|
// Generate certificates
|
||||||
certFile := filepath.Join(dir, fmt.Sprintf("%s.pem", domain))
|
certFile := filepath.Join(dir, cli.Sprintf("%s.pem", domain))
|
||||||
keyFile := filepath.Join(dir, fmt.Sprintf("%s-key.pem", domain))
|
keyFile := filepath.Join(dir, cli.Sprintf("%s-key.pem", domain))
|
||||||
|
|
||||||
// mkcert generates cert and key with specific naming
|
// mkcert generates cert and key with specific naming
|
||||||
genCmd := exec.Command("mkcert",
|
genCmd := exec.Command("mkcert",
|
||||||
|
|
@ -103,7 +104,7 @@ func SetupSSL(domain string, opts SSLOptions) error {
|
||||||
)
|
)
|
||||||
|
|
||||||
if output, err := genCmd.CombinedOutput(); err != nil {
|
if output, err := genCmd.CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("failed to generate certificates: %w\n%s", err, output)
|
return cli.Err("failed to generate certificates: %v\n%s", err, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -134,13 +135,13 @@ func IsMkcertInstalled() bool {
|
||||||
// InstallMkcertCA installs the local CA for mkcert.
|
// InstallMkcertCA installs the local CA for mkcert.
|
||||||
func InstallMkcertCA() error {
|
func InstallMkcertCA() error {
|
||||||
if !IsMkcertInstalled() {
|
if !IsMkcertInstalled() {
|
||||||
return fmt.Errorf("mkcert is not installed")
|
return cli.Err("mkcert is not installed")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("mkcert", "-install")
|
cmd := exec.Command("mkcert", "-install")
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to install mkcert CA: %w\n%s", err, output)
|
return cli.Err("failed to install mkcert CA: %v\n%s", err, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -149,13 +150,13 @@ func InstallMkcertCA() error {
|
||||||
// GetMkcertCARoot returns the path to the mkcert CA root directory.
|
// GetMkcertCARoot returns the path to the mkcert CA root directory.
|
||||||
func GetMkcertCARoot() (string, error) {
|
func GetMkcertCARoot() (string, error) {
|
||||||
if !IsMkcertInstalled() {
|
if !IsMkcertInstalled() {
|
||||||
return "", fmt.Errorf("mkcert is not installed")
|
return "", cli.Err("mkcert is not installed")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("mkcert", "-CAROOT")
|
cmd := exec.Command("mkcert", "-CAROOT")
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get mkcert CA root: %w", err)
|
return "", cli.WrapVerb(err, "get", "mkcert CA root")
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Clean(string(output)), nil
|
return filepath.Clean(string(output)), nil
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ package php
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/host-uk/core/pkg/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestOptions configures PHP test execution.
|
// TestOptions configures PHP test execution.
|
||||||
|
|
@ -58,7 +59,7 @@ func RunTests(ctx context.Context, opts TestOptions) error {
|
||||||
if opts.Dir == "" {
|
if opts.Dir == "" {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return cli.WrapVerb(err, "get", "working directory")
|
||||||
}
|
}
|
||||||
opts.Dir = cwd
|
opts.Dir = cwd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue