Skip to content
Open
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
65 changes: 55 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

This is the Smartling CLI tool - a command-line interface for managing translation files through the Smartling platform. The CLI provides commands for file operations (push, pull, list, status, rename, delete, import), project management, and machine translation services.
This is the Smartling CLI tool - a command-line interface for managing translation files through the Smartling platform. The CLI provides commands for file operations (push, pull, list, status, rename, delete, import), project management, machine translation services, and job progress tracking.

## Architecture

Expand All @@ -14,8 +14,9 @@ The codebase follows a layered architecture pattern:
- Each command has its own subdirectory with command definition and tests
- Service initializers are used to inject dependencies into commands
- **services/**: Business logic layer containing service implementations
- Each service corresponds to a command group (files, projects, init, mt)
- Each service corresponds to a command group (files, projects, init, mt, jobs)
- Services interact with the Smartling API SDK
- **services/helpers/**: Shared utilities for config management, error handling, progress rendering, format compilation, and thread pooling
- **output/**: Rendering and formatting layer for CLI output
- **main.go**: Entry point that wires together all commands and their dependencies

Expand All @@ -24,24 +25,56 @@ The CLI uses dependency injection through service initializers, making it testab
## Common Development Commands

### Build

The project uses a Makefile-based build system that cross-compiles for multiple platforms:

```bash
make all # Clean, get dependencies, and build for all platforms
make build # Build for darwin, windows, linux
go build # Build for current platform
make build # Build for darwin, windows, linux (outputs to bin/ directory)
go build # Build for current platform only
```

**Build outputs:**
- `bin/smartling.darwin` - macOS binary
- `bin/smartling.windows.exe` - Windows binary
- `bin/smartling.linux` - Linux binary

**Build time:** Typically takes 10-30 seconds depending on whether dependencies need to be downloaded.

### Testing

The project has two test suites:

#### Unit Tests
Unit tests cover all command and service logic with mocked dependencies:

```bash
make test_unit # Run unit tests
make test_integration # Run integration tests (requires binary in tests/cmd/bin/)
go test ./cmd/... # Run specific unit tests
make test_unit # Run all unit tests (recommended)
go test ./cmd/... # Run all unit tests in cmd/
go test ./cmd/files/push/ # Run tests for a specific command
go test -v -run TestSpecificFunction ./cmd/... # Run specific test function with verbose output
```

**Expected results:** All unit tests should pass. Test suite includes 13+ test packages covering commands for files, projects, machine translation, and initialization.

**Test time:** Unit tests typically complete in 2-5 seconds (many results are cached).

#### Integration Tests
Integration tests require a built binary and test the CLI end-to-end:

```bash
make test_integration # Run all integration tests (requires binary in tests/cmd/bin/)
go test ./tests/cmd/files/push/... # Run specific integration test
```

**Prerequisites:** Binary must be built and placed in `tests/cmd/bin/` before running integration tests.

### Code Quality
```bash
make lint # Run revive linter
make lint # Run golangci-lint and revive linter
make tidy # Clean up go.mod
make mockery # Generate mocks using mockery
make mockery # Generate mocks using mockery (config: .mockery.yml)
make docs # Generate command documentation
```

### Package Building
Expand Down Expand Up @@ -82,4 +115,16 @@ The CLI uses YAML configuration files (smartling.yml) that can be placed in the
4. Output formatting is handled in output/ package
5. Configuration is managed through config helpers

The service initializer pattern allows for clean dependency injection and makes the codebase highly testable.
### Service Initializer Pattern

Each command group (files, projects, init, mt, jobs) follows the same dependency injection pattern:

1. **Command Group** (e.g., `cmd/files/cmd_files.go`): Defines the `SrvInitializer` interface and factory function
2. **Service Initializer** (e.g., `cmd/files/cmd_files.go`): Implements the initializer that wires up SDK clients and configuration
3. **Service** (e.g., `services/files/service.go`): Defines the Service interface with business logic methods
4. **Command Implementation** (e.g., `cmd/files/push/cmd_push.go`): Uses the initializer to get service instance and executes operations

This pattern enables:
- Easy mocking of services in command tests
- Centralized client and configuration setup
- Clear separation between CLI interface and business logic
16 changes: 15 additions & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,23 @@ pipeline {
}

stages {
stage('Build') {
stage('Run Tests') {
steps {
sh "docker pull golang"
sh """docker run -t --rm -v \${WORKSPACE}:/go/src/cli -w /go/src/cli golang bash -c '
go install github.com/jstemmer/go-junit-report/v2@latest && \\
go test -v ./cmd/... 2>&1 | tee /tmp/test-output.txt | /go/bin/go-junit-report -set-exit-code > test-results.xml
'"""
}
post {
always {
junit 'test-results.xml'
}
}
}

stage('Build') {
steps {
sh "docker run -t --rm -v ${WORKSPACE}:/go/src/cli -w /go/src/cli golang make"
}
}
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@az-smartling I updated the Makefile because I keep having the same issue when I try to build something. Please let me know if this fix is not correct.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ build: darwin windows.exe linux

get:
go get
go mod vendor

clean:
rm -rf bin pkg
Expand Down Expand Up @@ -59,7 +60,7 @@ _pkg-init:
$(shell git rev-list --count HEAD).$(shell git rev-parse --short HEAD))

%:
GOOS=$(basename $@) go build -o bin/smartling.$@
GOOS=$(basename $@) go build -mod=mod -o bin/smartling.$@

docs:
go run ./main.go docs
Expand Down Expand Up @@ -87,6 +88,7 @@ test_unit:
test_integration:
go test ./tests/cmd/files/push/...
go test ./tests/cmd/files/pull/...
go test ./tests/cmd/jobs/progress/...
go test ./tests/cmd/files/list/...
go test ./tests/cmd/files/status/...
go test ./tests/cmd/files/rename/...
Expand Down
File renamed without changes.
39 changes: 39 additions & 0 deletions cmd/helpers/resolve/output_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package resolve

import (
"github.com/Smartling/smartling-cli/output"
clierror "github.com/Smartling/smartling-cli/services/helpers/cli_error"

"github.com/spf13/cobra"
)

// OutputParams resolve OutputParams for subcommands
func OutputParams(cmd *cobra.Command, fileConfigMTFileFormat *string) (output.Params, error) {
const outputTemplateFlag = "format"
format, err := cmd.Parent().PersistentFlags().GetString("output")
if err != nil {
return output.Params{}, clierror.UIError{
Operation: "get output",
Err: err,
Description: "unable to get output param",
}
}
template := FallbackString(cmd.Flags().Lookup(outputTemplateFlag), StringParam{
FlagName: outputTemplateFlag,
Config: fileConfigMTFileFormat,
})

mode, err := cmd.Parent().PersistentFlags().GetString("output-mode")
if err != nil {
return output.Params{}, clierror.UIError{
Operation: "get output mode",
Err: err,
Description: "unable to get output mode param",
}
}
return output.Params{
Mode: mode,
Format: format,
Template: template,
}, nil
}
4 changes: 1 addition & 3 deletions cmd/init/cmd_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import (
"github.com/spf13/cobra"
)

var (
dryRun bool
)
var dryRun bool

// NewInitCmd creates a new command to initialize the Smartling CLI.
func NewInitCmd(srvInitializer SrvInitializer) *cobra.Command {
Expand Down
70 changes: 70 additions & 0 deletions cmd/jobs/cmd_jobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package jobs

import (
"fmt"
"slices"
"strings"

"github.com/spf13/cobra"
)

const (
outputFormatFlag = "output"
)

var (
outputFormat string
allowedOutputs = []string{
"json",
"simple",
}
joinedAllowedOutputs = strings.Join(allowedOutputs, ", ")
)

// NewJobsCmd returns new jobs command
func NewJobsCmd() *cobra.Command {
jobsCmd := &cobra.Command{
Use: "jobs",
Short: "Manage translation jobs and monitor their progress.",
Long: `Translation jobs are the fundamental unit of work in Smartling TMS that organize
content for translation and track it through the translation workflow.

The jobs command group provides tools to interact with translation jobs, including
monitoring translation progress, viewing job details, and managing job workflows.

Each job contains one or more files targeted for translation into specific locales,
with defined due dates and workflow steps. Jobs help coordinate translation work between
content owners, project managers, and translators.

Available options:
--output string Output format: ` + joinedAllowedOutputs + ` (default "simple")
- simple: Human-readable format optimized for terminal display
- json: Raw API response for programmatic processing and automation`,
Example: `
# View job progress in human-readable format

smartling-cli jobs progress "Website Q1 2026"

# Get detailed progress data for automation

smartling-cli jobs progress aabbccdd --output json

`,
PreRunE: func(cmd *cobra.Command, args []string) error {
if !slices.Contains(allowedOutputs, outputFormat) {
return fmt.Errorf("invalid output: %s (allowed: %s)", outputFormat, joinedAllowedOutputs)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 && cmd.Flags().NFlag() == 0 {
return cmd.Help()
}
return nil
},
}

jobsCmd.PersistentFlags().StringVar(&outputFormat, outputFormatFlag, "simple", "Output format: "+joinedAllowedOutputs)

return jobsCmd
}
Loading