Skip to content

Phase 2: Extract shared atlassian-go package #2

@rianjs

Description

@rianjs

What

Create a shared Go module (github.com/open-cli-collective/atlassian-go) containing common code used by both CLIs.

Shared Components to Extract

Package Purpose Source Files
auth/ HTTP Basic Auth header generation api/client.go from both
client/ Base HTTP client (do, Get, Post, Put, Delete) api/client.go from both
config/ Environment variable precedence (GetWithFallback) internal/config/config.go from both
errors/ APIError struct, sentinel errors api/errors.go (jtk), api/client.go (cfl)
view/ Output renderer (table/JSON/plain) internal/view/view.go from both

Target Structure

shared/
├── go.mod                    # github.com/open-cli-collective/atlassian-go
├── auth/
│   ├── auth.go              # BasicAuthHeader(email, token) string
│   └── auth_test.go
├── client/
│   ├── client.go            # Client struct, Do, Get, Post, Put, Delete
│   ├── client_test.go
│   └── options.go           # ClientOptions (timeout, verbose, etc.)
├── config/
│   ├── env.go               # GetEnvWithFallback(primary, fallback string) string
│   └── env_test.go
├── errors/
│   ├── errors.go            # APIError, ErrNotFound, ErrUnauthorized, etc.
│   └── errors_test.go
└── view/
    ├── view.go              # Renderer, Format enum
    ├── view_test.go
    ├── table.go             # Table formatting
    └── json.go              # JSON formatting

API Design

// auth/auth.go
package auth

func BasicAuthHeader(email, apiToken string) string

// client/client.go  
package client

type Client struct {
    BaseURL    string
    HTTPClient *http.Client
    AuthHeader string
    Verbose    bool
}

type Options struct {
    Timeout time.Duration
    Verbose bool
}

func New(baseURL, email, apiToken string, opts *Options) *Client
func (c *Client) Do(ctx context.Context, method, path string, body interface{}) ([]byte, error)
func (c *Client) Get(ctx context.Context, path string) ([]byte, error)
func (c *Client) Post(ctx context.Context, path string, body interface{}) ([]byte, error)
func (c *Client) Put(ctx context.Context, path string, body interface{}) ([]byte, error)
func (c *Client) Delete(ctx context.Context, path string) ([]byte, error)

// config/env.go
package config

func GetEnvWithFallback(primary, fallback string) string

// errors/errors.go
package errors

var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrForbidden    = errors.New("forbidden")
)

type APIError struct {
    StatusCode int
    Message    string
    Errors     []string
}

func (e *APIError) Error() string
func ParseAPIError(resp *http.Response, body []byte) error

// view/view.go
package view

type Format string
const (
    FormatTable Format = "table"
    FormatJSON  Format = "json"
    FormatPlain Format = "plain"
)

type Renderer struct { ... }
func New(format Format, w io.Writer, noColor bool) *Renderer
func (r *Renderer) Table(headers []string, rows [][]string) error
func (r *Renderer) JSON(v interface{}) error
func (r *Renderer) Success(msg string)
func (r *Renderer) Error(msg string)

Why

DRY (Don't Repeat Yourself)

Both CLIs have nearly identical implementations of:

  • HTTP Basic Auth (same algorithm, same headers)
  • HTTP client wrapper (same timeout, same JSON handling, same error parsing)
  • Output formatting (same table/JSON/plain logic)
  • Environment variable fallback (same ATLASSIAN_* pattern)

Duplicated code means:

  • Bug fixes need to be applied twice
  • Improvements diverge over time
  • Testing effort is doubled
  • New contributors learn two slightly different patterns

Public module enables ecosystem

By making this a public Go module (github.com/open-cli-collective/atlassian-go), we enable:

  • Other Atlassian CLI tools (Bitbucket, Trello) to share the same foundation
  • External projects to import our battle-tested Atlassian client
  • Community contributions to the shared layer benefit all tools

Separation of concerns

The shared module has a clear contract:

  • It knows about: HTTP, JSON, auth headers, output formatting
  • It doesn't know about: Confluence pages, Jira issues, specific API endpoints

This keeps the shared code stable while tool-specific code evolves independently.

Acceptance Criteria

  • shared/go.mod exists with module path github.com/open-cli-collective/atlassian-go
  • All shared packages have tests with >80% coverage
  • go build ./shared/... succeeds
  • go test ./shared/... passes
  • API is documented with GoDoc comments
  • No tool-specific code (Confluence/Jira) in shared packages

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions