Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
branches: [ main ]
workflow_dispatch:

defaults:
run:
working-directory: cli

env:
GO_VERSION: '1.26.0'

Expand Down Expand Up @@ -49,20 +53,19 @@ jobs:

- name: Format check
run: |
if [ -n "$(gofmt -l ./cli/src/...)" ]; then
if [ -n "$(gofmt -l ./src/)" ]; then
echo "Go files need formatting:"
gofmt -l ./cli/src/...
gofmt -l ./src/
exit 1
fi

- name: Lint
run: golangci-lint run --timeout=5m ./cli/src/...
run: golangci-lint run --timeout=5m ./src/...

- name: Security scan
run: gosec -quiet -exclude=G204,G304 ./cli/src/...
run: gosec -quiet -exclude=G204,G304 ./src/...

- name: Vulnerability check
working-directory: cli
run: govulncheck ./src/...

test:
Expand Down Expand Up @@ -109,11 +112,11 @@ jobs:
- name: Run tests
shell: bash
run: |
mkdir -p coverage
mkdir -p ../coverage
if [ "${{ matrix.os }}" = "macos-latest" ]; then
go test -short -v -coverprofile=coverage/coverage.out ./cli/src/...
go test -short -v -coverprofile=../coverage/coverage.out ./src/...
else
go test -short -v -race -coverprofile=coverage/coverage.out ./cli/src/...
go test -short -v -race -coverprofile=../coverage/coverage.out ./src/...
fi

- name: Upload coverage to Codecov
Expand All @@ -130,12 +133,12 @@ jobs:
- name: Generate coverage report
if: matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request'
run: |
go tool cover -func=coverage/coverage.out -o=coverage/coverage.txt
go tool cover -func=../coverage/coverage.out -o=../coverage/coverage.txt
echo "## Code Coverage Report" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat coverage/coverage.txt | tail -n 20 >> $GITHUB_STEP_SUMMARY
cat ../coverage/coverage.txt | tail -n 20 >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
COVERAGE=$(go tool cover -func=coverage/coverage.out | grep total | awk '{print $3}')
COVERAGE=$(go tool cover -func=../coverage/coverage.out | grep total | awk '{print $3}')
echo "**Total Coverage: $COVERAGE**" >> $GITHUB_STEP_SUMMARY

lint:
Expand Down Expand Up @@ -166,7 +169,7 @@ jobs:
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest

- name: Run golangci-lint
run: golangci-lint run --timeout=5m ./cli/src/...
run: golangci-lint run --timeout=5m ./src/...

build:
name: Build
Expand Down Expand Up @@ -195,13 +198,13 @@ jobs:

- name: Build for multiple platforms
run: |
GOOS=windows GOARCH=amd64 go build -o bin/windows-amd64/copilot.exe ./cli/src/cmd/copilot
GOOS=linux GOARCH=amd64 go build -o bin/linux-amd64/copilot ./cli/src/cmd/copilot
GOOS=darwin GOARCH=amd64 go build -o bin/darwin-amd64/copilot ./cli/src/cmd/copilot
GOOS=darwin GOARCH=arm64 go build -o bin/darwin-arm64/copilot ./cli/src/cmd/copilot
GOOS=windows GOARCH=amd64 go build -o bin/windows-amd64/copilot.exe ./src/cmd/copilot
GOOS=linux GOARCH=amd64 go build -o bin/linux-amd64/copilot ./src/cmd/copilot
GOOS=darwin GOARCH=amd64 go build -o bin/darwin-amd64/copilot ./src/cmd/copilot
GOOS=darwin GOARCH=arm64 go build -o bin/darwin-arm64/copilot ./src/cmd/copilot

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries
path: bin/
path: cli/bin/
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ testcov.txt
COVERAGE_IMPROVEMENT_SUMMARY.md
COVERAGE_REPORT.md

# Go workspace files are committed (needed for module resolution)
# go.work
# go.work.sum
# Go workspace files (local dev only, not for CI)
go.work
go.work.sum

# IDE
.vscode/
Expand Down
2 changes: 1 addition & 1 deletion cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0
github.com/azure/azure-dev/cli/azd v0.0.0-20260205194320-e04533f58fa7
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
github.com/jongio/azd-core v0.5.0
github.com/jongio/azd-core v0.5.1
github.com/magefile/mage v1.15.0
github.com/mark3labs/mcp-go v0.43.2
github.com/spf13/cobra v1.10.2
Expand Down
4 changes: 2 additions & 2 deletions cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ github.com/jmespath-community/go-jmespath v1.1.1 h1:bFikPhsi/FdmlZhVgSCd2jj1e7G/
github.com/jmespath-community/go-jmespath v1.1.1/go.mod h1:4gOyFJsR/Gk+05RgTKYrifT7tBPWD8Lubtb5jRrfy9I=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jongio/azd-core v0.5.0 h1:QjV7lLz/IoXPmT/LZ05nYPgB/wU8uJK2Wg8T+SGss+M=
github.com/jongio/azd-core v0.5.0/go.mod h1:d6S8InR9GR0Aw1+y6kvEVZoMoSqoACsckj/2mNT6nf0=
github.com/jongio/azd-core v0.5.1 h1:xrAWyRIjZFVF0EOTgwjEbcMzU8wpvI1xvp6pqiDhHxU=
github.com/jongio/azd-core v0.5.1/go.mod h1:jQCP+px3Pxb3B0fyShfvSVa3KsWT1j2jGXMsPpQezlI=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down
2 changes: 1 addition & 1 deletion cli/magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func getVersion() (string, error) {

// All runs lint, test, and build in dependency order.
func All() error {
mg.Deps(Fmt, Lint, Test)
mg.SerialDeps(Fmt, Lint, Test)
return Build()
}

Expand Down
53 changes: 10 additions & 43 deletions cli/src/cmd/copilot/commands/version.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package commands

import (
"encoding/json"
"os"

"github.com/jongio/azd-core/cliout"
coreversion "github.com/jongio/azd-core/version"
"github.com/spf13/cobra"
)

Expand All @@ -17,46 +14,16 @@ var BuildTime = "unknown"
// Commit is set at build time via -ldflags.
var Commit = "unknown"

// VersionInfo represents version information for JSON output.
type VersionInfo struct {
Version string `json:"version"`
BuildTime string `json:"buildTime"`
Commit string `json:"commit"`
// VersionInfo provides the shared version information for this extension.
var VersionInfo = coreversion.New("jongio.azd.copilot", "azd copilot")

func init() {
VersionInfo.Version = Version
VersionInfo.BuildDate = BuildTime
VersionInfo.GitCommit = Commit
}

// NewVersionCommand creates the version command.
func NewVersionCommand() *cobra.Command {
var jsonOutput bool

cmd := &cobra.Command{
Use: "version",
Short: "Show version information",
Long: `Display the version of the azd copilot extension.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if jsonOutput {
info := VersionInfo{
Version: Version,
BuildTime: BuildTime,
Commit: Commit,
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
return encoder.Encode(info)
}

cliout.Section("🤖", "Azure Copilot CLI Extension")
cliout.Newline()
cliout.Label("Version", Version)
cliout.Label("Built", BuildTime)
if Commit != "unknown" {
cliout.Label("Commit", Commit)
}
return nil
},
}

cmd.Flags().BoolVar(&jsonOutput, "json", false, "Output version as JSON")

return cmd
func NewVersionCommand(outputFormat *string) *cobra.Command {
return coreversion.NewCommand(VersionInfo, outputFormat)
}
106 changes: 15 additions & 91 deletions cli/src/cmd/copilot/commands/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,142 +5,66 @@ package commands

import (
"bytes"
"encoding/json"
"testing"
)

func TestVersionConstants(t *testing.T) {
// Version should be set (either to "dev" or a version number)
if Version == "" {
t.Error("Version should not be empty")
}

// BuildTime should be set
if BuildTime == "" {
t.Error("BuildTime should not be empty")
}

// Commit should be set
if Commit == "" {
t.Error("Commit should not be empty")
}

t.Logf("Version: %s, BuildTime: %s, Commit: %s", Version, BuildTime, Commit)
}

func TestVersionInfo_Fields(t *testing.T) {
info := VersionInfo{
Version: "1.0.0",
BuildTime: "2024-01-15T10:30:00Z",
Commit: "abc123def",
func TestVersionInfo(t *testing.T) {
if VersionInfo == nil {
t.Fatal("VersionInfo should not be nil")
}

if info.Version != "1.0.0" {
t.Errorf("VersionInfo.Version = %q, want %q", info.Version, "1.0.0")
if VersionInfo.ExtensionID != "jongio.azd.copilot" {
t.Errorf("VersionInfo.ExtensionID = %q, want %q", VersionInfo.ExtensionID, "jongio.azd.copilot")
}
if info.BuildTime != "2024-01-15T10:30:00Z" {
t.Errorf("VersionInfo.BuildTime = %q, want %q", info.BuildTime, "2024-01-15T10:30:00Z")
if VersionInfo.Name != "azd copilot" {
t.Errorf("VersionInfo.Name = %q, want %q", VersionInfo.Name, "azd copilot")
}
if info.Commit != "abc123def" {
t.Errorf("VersionInfo.Commit = %q, want %q", info.Commit, "abc123def")
}
}

func TestVersionInfo_JSON(t *testing.T) {
info := VersionInfo{
Version: "1.0.0",
BuildTime: "2024-01-15T10:30:00Z",
Commit: "abc123",
}

data, err := json.Marshal(info)
if err != nil {
t.Fatalf("json.Marshal() error = %v", err)
}

// Verify JSON contains expected fields
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("json.Unmarshal() error = %v", err)
}

if parsed["version"] != "1.0.0" {
t.Errorf("JSON version = %v, want %q", parsed["version"], "1.0.0")
}
if parsed["buildTime"] != "2024-01-15T10:30:00Z" {
t.Errorf("JSON buildTime = %v, want %q", parsed["buildTime"], "2024-01-15T10:30:00Z")
}
if parsed["commit"] != "abc123" {
t.Errorf("JSON commit = %v, want %q", parsed["commit"], "abc123")
if VersionInfo.Version == "" {
t.Error("VersionInfo.Version should not be empty")
}
}

func TestNewVersionCommand(t *testing.T) {
cmd := NewVersionCommand()
outputFormat := "default"
cmd := NewVersionCommand(&outputFormat)

if cmd == nil {
t.Fatal("NewVersionCommand() returned nil")
}

// Check command metadata
if cmd.Use != "version" {
t.Errorf("cmd.Use = %q, want %q", cmd.Use, "version")
}
if cmd.Short == "" {
t.Error("cmd.Short should not be empty")
}
if cmd.Long == "" {
t.Error("cmd.Long should not be empty")
}

// Check that --json flag exists
flag := cmd.Flags().Lookup("json")
flag := cmd.Flags().Lookup("quiet")
if flag == nil {
t.Error("--json flag should exist")
t.Error("--quiet flag should exist")
}
}

func TestVersionCommand_DefaultOutput(t *testing.T) {
cmd := NewVersionCommand()
outputFormat := "default"
cmd := NewVersionCommand(&outputFormat)

// Capture output
var stdout bytes.Buffer
cmd.SetOut(&stdout)

// Execute command
err := cmd.Execute()
if err != nil {
t.Fatalf("cmd.Execute() error = %v", err)
}

// Note: Output verification depends on cliout implementation
// Just verify no error occurred
}

func TestVersionCommand_JSONOutput(t *testing.T) {
cmd := NewVersionCommand()

// Set --json flag
if err := cmd.Flags().Set("json", "true"); err != nil {
t.Fatalf("Failed to set flag: %v", err)
}

// Capture output
var stdout bytes.Buffer
cmd.SetOut(&stdout)

// Execute command - note this writes to os.Stdout in the current implementation
// This test verifies the command doesn't error with --json flag
err := cmd.Execute()
if err != nil {
t.Fatalf("cmd.Execute() with --json error = %v", err)
}
}

func TestVersionCommand_SilenceUsage(t *testing.T) {
cmd := NewVersionCommand()

if !cmd.SilenceUsage {
t.Error("cmd.SilenceUsage should be true")
}
}
Loading
Loading