Update pipeline files and clean up refactoring documents

This commit is contained in:
toki 2026-04-04 18:02:44 +09:00
parent a54f54ef20
commit 45e79bdebd
10 changed files with 103 additions and 192 deletions

0
.codex Normal file
View file

View file

@ -6,6 +6,14 @@ import 'package:oto_cli/oto/data/command_data.dart';
import 'package:oto_cli/oto/pipeline/pipeline.dart'; import 'package:oto_cli/oto/pipeline/pipeline.dart';
import 'package:oto_cli/oto/pipeline/pipeline_condition.dart'; import 'package:oto_cli/oto/pipeline/pipeline_condition.dart';
/// Base class for all pipeline executors.
///
/// Example YAML (workflow snippet):
/// ```yaml
/// workflow:
/// - exe: buildIos # execute a command by ID
/// - async: uploadSymbols # execute a command asynchronously (non-blocking)
/// ```
abstract class PipelineExecutor { abstract class PipelineExecutor {
//set & validate data //set & validate data
PipelineValidateResult initialize(Map<String, dynamic> set); PipelineValidateResult initialize(Map<String, dynamic> set);
@ -29,6 +37,12 @@ abstract class PipelineExecutor {
} }
} }
/// Executes a single command synchronously by its command ID.
///
/// Example YAML:
/// ```yaml
/// - exe: buildIos
/// ```
class PipelineExe extends PipelineExecutor { class PipelineExe extends PipelineExecutor {
late String commandID; late String commandID;
String printPrefix = ''; String printPrefix = '';
@ -79,6 +93,12 @@ class PipelineExe extends PipelineExecutor {
} }
} }
/// Executes a command asynchronously (fire-and-forget, does not wait for completion).
///
/// Example YAML:
/// ```yaml
/// - async: uploadSymbols
/// ```
class PipelineAsync extends PipelineExe { class PipelineAsync extends PipelineExe {
@override @override
Future execute() async { Future execute() async {

View file

@ -12,6 +12,17 @@ class ExeHandleData {
late Pipeline? pipelineFail; late Pipeline? pipelineFail;
} }
/// Executes a command and branches to on-success or on-fail pipeline based on result.
///
/// Example YAML:
/// ```yaml
/// - exe-handle:
/// id: buildIos
/// on-success:
/// - exe: notifySuccess
/// on-fail:
/// - exe: notifyFail
/// ```
class PipelineExeHandle extends PipelineExecutor { class PipelineExeHandle extends PipelineExecutor {
late DataExeHandle data; late DataExeHandle data;
late String commandID; late String commandID;

View file

@ -6,6 +6,27 @@ import 'package:oto_cli/oto/data/pipeline_data.dart';
import 'package:oto_cli/oto/pipeline/pipeline.dart'; import 'package:oto_cli/oto/pipeline/pipeline.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe.dart'; import 'package:oto_cli/oto/pipeline/pipeline_exe.dart';
/// Iterates over a List or Map and executes on-do pipeline for each element.
/// - List: sets setValue for each item.
/// - Map: sets setKey (optional) and setValue for each key-value pair.
///
/// Example YAML:
/// ```yaml
/// # List iteration
/// - foreach:
/// iterator: <!property.arr>
/// setValue: <@property.item>
/// on-do:
/// - exe: processItem
///
/// # Map iteration
/// - foreach:
/// iterator: <!property.map>
/// setKey: <@property.key> # optional
/// setValue: <@property.value>
/// on-do:
/// - exe: processEntry
/// ```
class PipelineForeach extends PipelineExecutor { class PipelineForeach extends PipelineExecutor {
late Pipeline? _pipelineOnDo; late Pipeline? _pipelineOnDo;
late DataForeach data; late DataForeach data;

View file

@ -4,6 +4,19 @@ import 'package:oto_cli/oto/pipeline/pipeline.dart';
import 'package:oto_cli/oto/pipeline/pipeline_condition.dart'; import 'package:oto_cli/oto/pipeline/pipeline_condition.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe.dart'; import 'package:oto_cli/oto/pipeline/pipeline_exe.dart';
/// Executes one of two pipelines based on a condition.
/// Condition types: condition-string, condition-int, condition-float,
/// condition-bool, condition-date, condition-version, condition-object
///
/// Example YAML:
/// ```yaml
/// - if:
/// condition-string: "<!property.env> == prod"
/// on-true:
/// - exe: deployProd
/// on-false:
/// - exe: deployDev
/// ```
class PipelineIf extends PipelineCondition { class PipelineIf extends PipelineCondition {
late Pipeline? _pipelineTrue; late Pipeline? _pipelineTrue;
late Pipeline? _pipelineFalse; late Pipeline? _pipelineFalse;

View file

@ -4,6 +4,21 @@ import 'package:oto_cli/oto/pipeline/pipeline.dart';
import 'package:oto_cli/oto/pipeline/pipeline_condition.dart'; import 'package:oto_cli/oto/pipeline/pipeline_condition.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe.dart'; import 'package:oto_cli/oto/pipeline/pipeline_exe.dart';
/// Executes tasks for the first matching case value.
/// Uses condition-string/int/etc. compared to each case value with ==.
///
/// Example YAML:
/// ```yaml
/// - switch:
/// condition-string: "<!property.env>"
/// cases:
/// - value: prod
/// tasks:
/// - exe: deployProd
/// - value: staging
/// tasks:
/// - exe: deployStaging
/// ```
class PipelineSwitch extends PipelineCondition { class PipelineSwitch extends PipelineCondition {
late Pipeline? _pipeline; late Pipeline? _pipeline;

View file

@ -4,6 +4,14 @@ import 'package:oto_cli/oto/pipeline/pipeline.dart';
import 'package:oto_cli/oto/pipeline/pipeline_condition.dart'; import 'package:oto_cli/oto/pipeline/pipeline_condition.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe.dart'; import 'package:oto_cli/oto/pipeline/pipeline_exe.dart';
/// Polls every 200ms and blocks until the condition becomes false.
/// Use wait-until-int / wait-until-double / wait-until-string depending on type.
///
/// Example YAML:
/// ```yaml
/// - wait-until-bool: "<!property.isUploading>" # waits while true
/// - wait-until-string: "<!property.status> == done" # waits while condition holds
/// ```
class PipelineWaitUntil extends PipelineCondition { class PipelineWaitUntil extends PipelineCondition {
PipelineWaitUntil(String type) { PipelineWaitUntil(String type) {
data = DataCondition(); data = DataCondition();
@ -30,6 +38,12 @@ class PipelineWaitUntil extends PipelineCondition {
} }
} }
/// Waits for a fixed number of seconds (supports fractional values).
///
/// Example YAML:
/// ```yaml
/// - wait-until-seconds: 2.5
/// ```
class PipelineWaitUntilSeconds extends PipelineExecutor { class PipelineWaitUntilSeconds extends PipelineExecutor {
PipelineWaitUntilSeconds(); PipelineWaitUntilSeconds();

View file

@ -4,6 +4,15 @@ import 'package:oto_cli/oto/pipeline/pipeline.dart';
import 'package:oto_cli/oto/pipeline/pipeline_condition.dart'; import 'package:oto_cli/oto/pipeline/pipeline_condition.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe.dart'; import 'package:oto_cli/oto/pipeline/pipeline_exe.dart';
/// Repeatedly executes on-do pipeline while the condition is true.
///
/// Example YAML:
/// ```yaml
/// - while:
/// condition-bool: "<!property.isRunning>"
/// on-do:
/// - exe: checkStatus
/// ```
class PipelineWhile extends PipelineCondition { class PipelineWhile extends PipelineCondition {
late Pipeline? _pipelineOnDo; late Pipeline? _pipelineOnDo;

View file

@ -1,94 +0,0 @@
# Refactoring History 001 — command_data.dart 도메인 분리
## 목표
AI(Claude 등)가 코드베이스를 더 효율적으로 읽고 수정할 수 있도록
하나의 거대한 파일에 집중된 데이터 클래스들을 도메인별로 분리한다.
## 배경
`command_data.dart` 파일에 100개 이상의 JSON 직렬화 데이터 클래스가
단일 파일에 존재했다. `json_serializable``build_runner`가 하나의
`command_data.g.dart`를 생성하는 구조라 관리 편의상 통합되어 있었으나,
AI가 단일 커맨드를 수정할 때도 전체 파일을 읽어야 해서 컨텍스트 낭비가 컸다.
AI가 코드 관리를 지원하는 환경에서는 파일 파편화 비용이 사라지므로
도메인 분리 결정을 내렸다.
## 변경 내용
### 새로 생성된 파일
| 파일 | 내용 | 클래스 수 |
|------|------|-----------|
| `lib/oto/data/base_data.dart` | DataParam, DataFile (모든 커맨드 데이터의 베이스) | 2 |
| `lib/oto/data/build_data.dart` | iOS/Flutter/MSBuild/Xcode/Testflight 등 빌드 관련 | 19 |
| `lib/oto/data/file_data.dart` | Copy/Delete/Rename/Zip/FileRead/FileWrite 등 | 10 |
| `lib/oto/data/git_data.dart` | Git/GitHub 커맨드 데이터 | 15 |
| `lib/oto/data/notification_data.dart` | Slack, Mattermost 발송 데이터 | 5 |
| `lib/oto/data/integration_data.dart` | Jira, Jenkins 연동 데이터 | 3 |
| `lib/oto/data/network_data.dart` | FTP, Web 요청 데이터 | 3 |
| `lib/oto/data/infra_data.dart` | Docker, AWS CLI, Gradle, Protobuf | 4 |
| `lib/oto/data/util_data.dart` | Shell, String, JSON, Process, SMB, Timer 등 | 20 |
### 수정된 파일
**`lib/oto/data/command_data.dart`**
- DataParam, DataBuildFlutter 등 모든 도메인 클래스 제거
- DataBuild, DataScheduler, DataCommand 3개만 유지 (파이프라인 루트 데이터)
- 도메인 파일 9개를 `export`하는 barrel 파일로 전환
### 영향받은 파일
없음. 기존 48개 파일의 `import 'package:oto_cli/oto/data/command_data.dart'`
barrel export를 통해 그대로 동작하므로 import 수정 불필요.
### 생성된 .g.dart 파일
`build_runner build --delete-conflicting-outputs` 실행으로 자동 생성됨:
- `base_data.g.dart`
- `build_data.g.dart`
- `file_data.g.dart`
- `git_data.g.dart`
- `notification_data.g.dart`
- `integration_data.g.dart`
- `network_data.g.dart`
- `infra_data.g.dart`
- `util_data.g.dart`
## 결과
- `dart analyze` 에러 0개 (기존 warning 1개 그대로)
- `build_runner` 278 outputs 성공
- AI가 특정 도메인 커맨드 수정 시 읽어야 할 코드량 대폭 감소
- 예: Flutter 빌드 수정 → `build_data.dart` (~330줄) 만 읽으면 됨
- 이전: `command_data.dart` (1300+ 줄) 전체를 읽어야 했음
## 현재 data/ 디렉토리 구조
```
lib/oto/data/
├── command_data.dart ← barrel (DataBuild, DataScheduler, DataCommand + exports)
├── command_data.g.dart
├── base_data.dart ← DataParam, DataFile 베이스 클래스
├── base_data.g.dart
├── build_data.dart ← 빌드 관련 (iOS, Flutter, MSBuild 등)
├── build_data.g.dart
├── file_data.dart ← 파일 시스템 operations
├── file_data.g.dart
├── git_data.dart ← Git, GitHub
├── git_data.g.dart
├── notification_data.dart ← Slack, Mattermost
├── notification_data.g.dart
├── integration_data.dart ← Jira, Jenkins
├── integration_data.g.dart
├── network_data.dart ← FTP, Web
├── network_data.g.dart
├── infra_data.dart ← Docker, AWS, Gradle, Protobuf
├── infra_data.g.dart
├── util_data.dart ← Shell, String, JSON, Process, Timer 등
├── util_data.g.dart
├── pipeline_data.dart ← 기존 파이프라인 구조 데이터 (변경 없음)
├── pipeline_data.g.dart
├── jenkins_data.dart ← Jenkins 환경변수 응답 데이터 (변경 없음)
├── jenkins_data.g.dart
├── jira_data.dart ← Jira API 응답 데이터 (변경 없음)
└── jira_data.g.dart
```
## 다음 작업
`refactoring/to-do.md` 참조

View file

@ -1,98 +0,0 @@
# AI-Friendly Refactoring To-Do
AI(Claude 등)가 코드베이스를 더 잘 이해하고 수정할 수 있도록 하는
리팩토링 작업 목록. 우선순위 순으로 정렬.
---
## ✅ 완료
### 1. command_data.dart 도메인별 분리
- **작업**: 1300줄짜리 단일 파일을 9개 도메인 파일로 분리
- **결과**: AI가 특정 커맨드 수정 시 읽어야 할 코드량 대폭 감소
- **상세**: `refactoring/history_001.md` 참조
### 2. commands/util/ 폴더 정리 — 잘못 배치된 커맨드 이동
- **작업**: `util/`의 도메인 혼재 파일 5개 이동, 중복 파일 1개 삭제
- **결과**:
- `util/slack.dart``notification/slack.dart`
- `util/mattermost.dart``notification/mattermost.dart`
- `util/publish_ios.dart``build/publish_ios.dart`
- `util/zip.dart``file/zip.dart`
- `util/smb_authorize.dart``infra/smb_authorize.dart`
- `util/jira.dart` → 삭제 (`jira/jira.dart`와 중복, import 없음)
- `command.dart` import 경로 5곳 수정
### 3. Self-registering Command 패턴
- **작업**: `command.dart`의 import 40개 + `_commandMap` 리터럴 78줄 제거, registry 패턴 도입
- **결과**:
- `Command.register()` static 메서드 추가
- 커맨드 파일 39개에 `registerXxx()` top-level 함수 추가
- `commands/command_registry.dart` 신규 생성 — 모든 커맨드 등록 중앙화
- `application.dart``build()` 초반에 `registerAllCommands()` 호출
- **새 커맨드 추가 절차**:
1. 커맨드 파일 작성 + 해당 파일의 `registerXxx()`에 1줄 추가
2. `command.dart` enum에 1줄 추가 → `build_runner` 재실행
3. `command_registry.dart`에 import + 함수 호출 추가 (신규 파일인 경우)
### 4. 태그 시스템 중앙화
- **작업**: 태그 파싱/치환 로직을 `command.dart`에서 분리
- **결과**:
- `lib/oto/core/tag_system.dart` 신규 생성 — `TagSystem` 클래스에 전체 구현 이동
- 태그 문법 레퍼런스 주석 포함 (`<!property.key>`, `<!state.cmdId>`, `<@property.key>`)
- `command.dart`의 태그 메서드는 `TagSystem`으로의 얇은 위임(delegate)만 유지
- 기존 `Command.replaceTagValue()` 등 외부 호출부는 수정 없이 그대로 동작
---
## 📋 할 일
### 5. Pipeline Executor에 YAML 예제 주석 추가
**우선순위: 중간 / 난이도: 낮음**
현재 `assets/yaml/example/` 폴더에 예제가 따로 있어 코드-예제 매핑이 안 된다.
각 PipelineExecutor 클래스 상단에 인라인 YAML 예제를 주석으로 추가하면
AI가 파이프라인 구조를 한 파일만 읽어도 파악할 수 있다.
대상 파일:
- `lib/oto/pipeline/pipeline_exe.dart`
- `lib/oto/pipeline/pipeline_if.dart`
- `lib/oto/pipeline/pipeline_foreach.dart`
- `lib/oto/pipeline/pipeline_while.dart`
- `lib/oto/pipeline/pipeline_switch.dart`
- `lib/oto/pipeline/pipeline_exe_handle.dart`
- `lib/oto/pipeline/pipeline_wait_until.dart`
예시:
```dart
/// Example YAML:
/// ```yaml
/// - if:
/// equal-string: "<!property.env>"
/// value: "prod"
/// on-true:
/// - exe: deployProd
/// on-false:
/// - exe: deployDev
/// ```
class PipelineIf extends PipelineExecutor { ... }
```
---
### 6. CLAUDE.md 작성 — 마지막에 작성
**우선순위: 높음 / 난이도: 낮음 / 시점: 모든 리팩토링 완료 후**
> ⚠️ 리팩토링이 진행되면 구조가 계속 바뀌므로 반드시 마지막에 작성한다.
프로젝트 루트에 `CLAUDE.md`를 두면 Claude가 대화 시작 시 자동으로 읽는다.
매 대화마다 프로젝트 구조를 다시 파악하는 비용을 없앨 수 있다.
포함할 내용:
- 프로젝트 개요 (Dart CLI, Jenkins 연동 빌드 자동화)
- 최종 디렉토리 구조 요약
- 새 커맨드 추가 방법 (단계별)
- 새 도메인 데이터 클래스 추가 방법 (`build_runner` 실행 포함)
- 태그 문법 레퍼런스 (`<!property.key>`, `<!state.cmdId>`, `<!common.field>`)
- 에러 핸들링 패턴 (`application.dart` catch 구조)
- 주의사항 (xcworkspace/xcodeproj `??` 패턴 등)