본문으로 건너뛰기

Architecture

union-cli는 YAML 선언 한 장으로 팀 전용 통합 CLI를 만드는 프레임워크입니다. 이 문서에서는 5-Layer 아키텍처, 빌드 파이프라인, 실행 흐름, 그리고 각 핵심 컴포넌트를 설명합니다.

먼저 읽으면 좋은 문서

Manifest Reference에서 YAML manifest 전체 스키마를 먼저 살펴보면 각 레이어의 역할이 더 잘 이해됩니다.


5-Layer 아키텍처 개요

union-cli는 5개의 레이어로 구성됩니다. 사용자는 Layer 1 (YAML manifest)만 작성하면 나머지는 프레임워크가 처리합니다.

Mini Map

Layer 1: Interface (YAML Manifest)

사용자가 작성하는 유일한 파일입니다. plugins/ 디렉토리에 YAML manifest를 배치합니다.

항목설명
역할CLI 커맨드, Provider 설정, 인증, 플래그를 선언적으로 정의
위치plugins/*.yaml, .union-cli/plugins/*.yaml, 또는 $UNION_CLI_PLUGINS_DIR
원칙코드 없이 YAML만으로 커맨드를 정의
name: my-api
namespace: api
provider:
type: http
config:
baseUrl: "https://api.example.com/v1"
commands:
- id: users:list
description: "사용자 목록"
http: { method: GET, path: "/users" }
정보

manifest 전체 스키마는 Manifest Reference를 참고하세요.


Layer 2: Build (빌드 파이프라인)

YAML manifest를 oclif 커맨드 JS 파일로 변환하는 빌드 단계입니다.

항목설명
역할YAML 탐색 → 파싱 → 스키마 검증 → 의미 검증 → JS 코드 생성
입력plugins/*.yaml
출력dist/commands/<namespace>/<topic>/<action>.js + .union-cli/manifest.json

자세한 빌드 과정은 빌드 파이프라인 섹션을 참고하세요.


Layer 3: CLI (oclif)

oclif 프레임워크가 제공하는 CLI 런타임 레이어입니다.

항목설명
역할커맨드 파싱, 표준 플래그(--json, --format, --quiet, --debug, --no-color), 도움말(--help), 자동완성
BaseCommand모든 생성된 커맨드가 상속하는 기본 클래스. 표준 플래그를 정의하고 출력 형식을 결정

Layer 4: Provider

실제 작업을 수행하는 실행 엔진입니다. 4가지 Provider 타입을 지원합니다.

Provider실행 방식사용 사례
HTTPfetch()REST API 호출
CLIchild_process.spawn()외부 CLI 바이너리 래핑 (kubectl, terraform 등)
PythonJSON-RPC over stdioPython SDK/라이브러리 호출
JSin-process ESM/CJS import()Node.js 모듈 직접 호출

Layer 5: Core Infrastructure

모든 레이어에서 공유하는 핵심 인프라입니다.

컴포넌트역할
Auth인증 헤더 생성 (Bearer, Basic, JWT, API-Key, Cookie)
Output출력 형식 변환 (table, json, yaml, csv)
Config설정 관리 (~/.my-cli/config.yaml)
CredentialStore인증 정보 저장 (.union-cli/tokens.json)
Error통합 에러 처리, 에러 코드, 사용자 친화적 메시지

실행 흐름

사용자가 커맨드를 실행하면 다음 순서로 처리됩니다.

$ my-cli api users create --name "John" --email "john@example.com" --json
Mini Map

실행 단계 요약

단계설명
1. oclif 초기화init hook 실행 → manifest.json 로드 → Executor/Provider 등록
2. 커맨드 파싱dist/commands/api/users/create.js 로드 → args, flags 파싱
3. Executor 호출CommandRegistry에서 CommandSpec 조회 → namespace로 Provider 선택
4. Provider 실행URL 구성, 파라미터 매핑, 인증 헤더 주입, fetch() 실행
5. 출력--json 플래그 감지 → JSON.stringify(result.data) → stdout 출력

빌드 파이프라인

npm run build 실행 시 다음 단계가 순서대로 진행됩니다.

flowchart LR
A["1. Discovery\nManifest 탐색"] --> B["2. Parse\nYAML 파싱 +\nJSON Schema 검증"]
B --> C["3. Validate\n의미 검증"]
C --> D["4. Codegen\nJS 코드 생성"]
D --> E["5. Cache\nmanifest.json 저장"]

1. Manifest 탐색 (Discovery)

4개 위치에서 YAML manifest 파일을 탐색합니다 (우선순위 순서).

순서경로설명
1.union-cli/plugins/*.yaml프로젝트 로컬
2plugins/*.yaml프로젝트 루트
3~/.<cliName>/plugins/*.yaml사용자 글로벌
4$UNION_CLI_PLUGINS_DIR/*.yaml환경변수 지정

2. YAML 파싱 (Parse)

각 YAML 파일을 파싱하고 JSON Schema(AJV)로 구조를 검증합니다.

  • 필수 필드: name, namespace, description, provider, commands
  • namespace 규칙: ^[a-z][a-z0-9-]*$
  • command ID 규칙: ^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*$ (topic:action 형태)

3. 의미 검증 (Validate)

검증 항목설명
중복 command ID같은 manifest 내에서 동일한 ID가 있으면 에러
Provider-Command 매칭HTTP provider인데 http 설정이 없으면 에러
표준 플래그 충돌--json, --debug, --quiet, --no-color, --format, --help과 이름이 같으면 에러
단축키 충돌-h, -q 단축키 사용 시 에러
민감 플래그 경고

password, secret, token 등의 이름을 가진 플래그는 경고가 출력됩니다. ps/history에 노출될 위험이 있으므로 SecretRef를 사용하세요.

4. 코드 생성 (Codegen)

각 manifest 커맨드에 대해 oclif Command JS 파일을 생성합니다.

plugins/my-api.yaml
commands:
- id: users:list → dist/commands/api/users/list.js
- id: users:create → dist/commands/api/users/create.js

추가로 Built-in 커맨드도 함께 생성됩니다: auth/login.js, auth/status.js, auth/logout.js, doctor.js

5. 캐시 저장

파싱된 manifest를 .union-cli/manifest.json에 캐시합니다. 이 파일은 CLI 실행 시 init hook에서 Executor와 Provider를 초기화하는 데 사용됩니다.


Init Hook 흐름

CLI가 실행될 때 oclif의 init hook이 가장 먼저 동작합니다.

flowchart TD
A["CLI 시작"] --> B["oclif init hook 실행"]
B --> C[".union-cli/manifest.json 읽기"]
C --> D{"각 manifest에 대해"}
D --> E["executor.registerManifest(manifest)\nCommandRegistry에 등록"]
D --> F["executor.registerProvider(namespace, provider)\nProvider 인스턴스 생성"]
E --> G["globalThis.__unionCliExecutor = executor\n전역 접근 가능"]
F --> G
codegen 커맨드와의 연결

codegen으로 생성된 커맨드 파일은 globalThis.__unionCliExecutor를 통해 Executor에 접근하여 커맨드를 실행합니다.


핵심 컴포넌트

CommandRegistry

커맨드 정의(CommandSpec)를 관리하는 중앙 레지스트리입니다.

항목설명
InterfaceCommandSpec -- id, namespace, description, args, flags, examples, providerType, providerConfig
Key Methodsregister(manifest), get(id), getByNamespace(ns), getAllSpecs(), getAllManifests()
Purpose중복 namespace 검사와 함께 PluginManifest를 등록하고, 전체 ID로 CommandSpec을 조회

Executor

Provider를 선택하고 커맨드 실행을 조율하는 오케스트레이터입니다.

항목설명
InterfaceregisterProvider(ns, provider), registerManifest(manifest), execute(specId, input): Promise<ExecutionResult>
ExecutionResultsuccess, data, exitCode (0/1/2), duration (ms), error? (code + message + details)
PurposeCommandRegistry에서 spec 조회 후 namespace에 매칭되는 Provider를 선택하여 실행

BaseCommand

모든 생성된 커맨드가 상속하는 oclif Command 기본 클래스입니다.

플래그단축키설명
--jsonJSON 출력
--debug디버그 출력 (에러 상세 포함)
--quiet-q최소 출력 (exit code만 반환)
--no-color색상/이모지 비활성화
--format출력 형식 (table, json, yaml, csv)

CredentialStore

인증 정보를 파일 시스템에 저장하고 관리합니다.

항목설명
FileCredentialStore파일 기반 (~/.my-cli/credentials/<namespace>.json), 파일 권한 0600
EnvCredentialStore환경변수 기반 (<NAMESPACE>_TOKEN), 읽기 전용
SecretRef 소스env (환경변수), file (파일), command (커맨드 실행), value (직접 값, 테스트용)
보안 주의

value는 YAML에 직접 비밀값을 노출하므로 테스트 환경에서만 사용하세요. 프로덕션에서는 env 또는 command를 권장합니다.


디렉토리 구조

src/
├── core/ # Layer 5: Core Infrastructure
│ ├── types.ts # 모든 TypeScript 인터페이스 정의
│ ├── registry.ts # CommandRegistry
│ ├── executor.ts # Executor
│ ├── base-command.ts # BaseCommand
│ ├── auth.ts # AuthManager
│ ├── credential-store.ts
│ ├── config.ts # ConfigManager
│ ├── output.ts # OutputFormatter
│ └── error.ts # UnifiedError

├── manifest/ # Layer 2: Build (파싱/검증)
│ ├── schema.ts # AJV JSON Schema
│ ├── parser.ts # YAML 파싱 + 스키마 검증
│ └── validator.ts # 의미 검증

├── providers/ # Layer 4: Provider
│ ├── http/ # HTTPProvider (fetch)
│ ├── cli/ # CLIProvider (spawn)
│ ├── python/ # PythonProvider (JSON-RPC)
│ └── js/ # JSProvider (in-process)

├── build/ # Layer 2: Build (코드 생성)
│ ├── discovery.ts # Manifest 파일 탐색
│ ├── builder.ts # 빌드 오케스트레이션
│ └── codegen.ts # oclif Command JS 코드 생성

├── commands/ # Built-in 커맨드
│ ├── auth/ # login, logout, status, token
│ ├── config/ # get, set, list, reset
│ ├── plugin/ # add, remove, list
│ └── ... # build, codegen, doctor, init

├── hooks/
│ └── init.ts # oclif init hook

└── index.ts # 공개 API export

환경변수 치환

manifest의 baseUrl 등에서 환경변수를 참조할 수 있습니다.

baseUrl: "${BASE_URL:-https://default.example.com}/api/v1"
문법설명
${VAR_NAME}환경변수 값으로 치환. 없으면 빈 문자열
${VAR_NAME:-기본값}환경변수가 없으면 기본값 사용

환경변수 치환에 대한 자세한 내용은 Manifest Reference - 환경변수 치환을 참고하세요.