From 144f46bf657a8097bff27343bce56f18e9a71bc4 Mon Sep 17 00:00:00 2001 From: toki Date: Sat, 4 Apr 2026 12:55:33 +0900 Subject: [PATCH] Refactor: Move util commands to appropriate directories and update command.dart --- .../commands/{util => build}/publish_ios.dart | 0 lib/oto/commands/command.dart | 10 +- lib/oto/commands/{util => file}/zip.dart | 0 .../{util => infra}/smb_authorize.dart | 0 .../{util => notification}/mattermost.dart | 0 .../{util => notification}/slack.dart | 0 lib/oto/commands/util/jira.dart | 118 ------------------ refactoring/to-do.md | 116 +++++++++++++++++ 8 files changed, 121 insertions(+), 123 deletions(-) rename lib/oto/commands/{util => build}/publish_ios.dart (100%) rename lib/oto/commands/{util => file}/zip.dart (100%) rename lib/oto/commands/{util => infra}/smb_authorize.dart (100%) rename lib/oto/commands/{util => notification}/mattermost.dart (100%) rename lib/oto/commands/{util => notification}/slack.dart (100%) delete mode 100644 lib/oto/commands/util/jira.dart create mode 100644 refactoring/to-do.md diff --git a/lib/oto/commands/util/publish_ios.dart b/lib/oto/commands/build/publish_ios.dart similarity index 100% rename from lib/oto/commands/util/publish_ios.dart rename to lib/oto/commands/build/publish_ios.dart diff --git a/lib/oto/commands/command.dart b/lib/oto/commands/command.dart index 50457a1..8aaa58b 100644 --- a/lib/oto/commands/command.dart +++ b/lib/oto/commands/command.dart @@ -29,10 +29,10 @@ import 'package:oto_cli/oto/commands/jira/jira.dart'; import 'package:oto_cli/oto/commands/process/process.dart'; import 'package:oto_cli/oto/commands/proto/protobuf.dart'; import 'package:oto_cli/oto/commands/util/json.dart'; -import 'package:oto_cli/oto/commands/util/mattermost.dart'; +import 'package:oto_cli/oto/commands/notification/mattermost.dart'; import 'package:oto_cli/oto/commands/util/print.dart'; import 'package:oto_cli/oto/commands/util/set_value.dart'; -import 'package:oto_cli/oto/commands/util/smb_authorize.dart'; +import 'package:oto_cli/oto/commands/infra/smb_authorize.dart'; import 'package:oto_cli/oto/commands/util/string_util.dart'; import 'package:oto_cli/oto/commands/util/timer.dart'; import 'package:oto_cli/oto/commands/web/web.dart'; @@ -42,9 +42,9 @@ import 'package:oto_cli/oto/commands/file/delete.dart'; import 'package:oto_cli/oto/commands/file/rename.dart'; import 'package:oto_cli/oto/commands/shell/shell.dart'; import 'package:oto_cli/oto/commands/file/copy.dart'; -import 'package:oto_cli/oto/commands/util/publish_ios.dart'; -import 'package:oto_cli/oto/commands/util/slack.dart'; -import 'package:oto_cli/oto/commands/util/zip.dart'; +import 'package:oto_cli/oto/commands/build/publish_ios.dart'; +import 'package:oto_cli/oto/commands/notification/slack.dart'; +import 'package:oto_cli/oto/commands/file/zip.dart'; import 'package:oto_cli/oto/commands/ftp/upload.dart'; import 'package:oto_cli/oto/pipeline/pipeline_exe_handle.dart'; diff --git a/lib/oto/commands/util/zip.dart b/lib/oto/commands/file/zip.dart similarity index 100% rename from lib/oto/commands/util/zip.dart rename to lib/oto/commands/file/zip.dart diff --git a/lib/oto/commands/util/smb_authorize.dart b/lib/oto/commands/infra/smb_authorize.dart similarity index 100% rename from lib/oto/commands/util/smb_authorize.dart rename to lib/oto/commands/infra/smb_authorize.dart diff --git a/lib/oto/commands/util/mattermost.dart b/lib/oto/commands/notification/mattermost.dart similarity index 100% rename from lib/oto/commands/util/mattermost.dart rename to lib/oto/commands/notification/mattermost.dart diff --git a/lib/oto/commands/util/slack.dart b/lib/oto/commands/notification/slack.dart similarity index 100% rename from lib/oto/commands/util/slack.dart rename to lib/oto/commands/notification/slack.dart diff --git a/lib/oto/commands/util/jira.dart b/lib/oto/commands/util/jira.dart deleted file mode 100644 index 3ec4fe1..0000000 --- a/lib/oto/commands/util/jira.dart +++ /dev/null @@ -1,118 +0,0 @@ -// ignore_for_file: prefer_function_declarations_over_variables, avoid_init_to_null - -import 'dart:convert'; - -import 'package:dart_framework/utils/slack/slack_data.dart'; -import 'package:dart_framework/utils/system_util.dart'; -import 'package:oto_cli/oto/application.dart'; -import 'package:oto_cli/oto/commands/command.dart'; -import 'package:oto_cli/oto/data/command_data.dart'; -import 'package:http/http.dart' as http; -import 'package:oto_cli/oto/data/jira_data.dart'; - -class Jira extends Command { - String? findContent(DataJiraContent? data) { - if (data != null) { - if (data.text != null) { - return data.text; - } else { - if (data.content != null && data.content!.isNotEmpty) { - return findContent(data.content!.first); - } else { - return null; - } - } - } else { - return null; - } - } - - @override - Future execute(DataCommand command) async { - var data = DataJira.fromJson(command.param); - var text = '${data.id}:${data.token}'; - var base64 = base64Encode(utf8.encode(text)); - var header = { - 'Content-Type': 'application/json', - 'Authorization': 'Basic $base64' - }; - var url = 'https://${data.domain}/rest/api/3/issue/${data.issue}'; - Application.log(url); - var response = await http.get(Uri.parse(url), headers: header); - - if (response.statusCode < 300 && response.statusCode >= 200) { - Application.log(response.body); - var issue = DataJiraIssue.fromJson(jsonDecode(response.body)); - var targetUser = issue.fields?.assignee?.emailAddress; - if (targetUser != null) { - var slackID = ''; - for (var user in data.users) { - if (targetUser.contains(user.mail!)) { - slackID = user.slackID!; - break; - } - } - - if (slackID.isNotEmpty) { - var arr = issue.fields?.description?.content; - var summary = issue.fields?.summary; - if ((arr != null && arr.isNotEmpty) || summary != null) { - String? content = ""; - if (arr != null && arr.isNotEmpty) { - content = findContent(arr.first); - content ??= summary!; - } else { - content = summary!; - } - var browseUrl = 'https://${data.domain}/browse/${data.issue}'; - DataSlack Function(String) getData = (channel) { - var slackData = DataSlack(channel); - - slackData.attachments = []; - var attachment = SlackAttachment(); - slackData.blocks = []; - var blockSection = SlackBlockSection(); - blockSection.text = SlackBlockText(); - blockSection.text!.text = ':jira: 새 Jira 업무가 할당되었습니다.'; - slackData.blocks!.add(blockSection); - - attachment.blocks = []; - - blockSection = SlackBlockSection(); - blockSection.text = SlackBlockText(); - blockSection.text!.type = SlackTextType.mrkdwn; - blockSection.text!.text = - '*<$browseUrl|${data.issue}> ${issue.fields?.summary}*'; - attachment.blocks!.add(blockSection); - - blockSection = SlackBlockSection(); - blockSection.text = SlackBlockText(); - blockSection.text!.text = content; - blockSection.accessory = SlackAccessory(); - blockSection.accessory!.type = SlackActionType.button; - blockSection.accessory!.url = browseUrl; - blockSection.accessory!.text = SlackBlockText(); - blockSection.accessory!.text!.type = SlackTextType.plain_text; - blockSection.accessory!.text!.text = 'Open'; - attachment.blocks!.add(blockSection); - - slackData.attachments!.add(attachment); - return slackData; - }; - // setReturn(data.return_slack_data, getData(slackID)); - - // if(data.sub_channel != null) { - // setReturn(data.return_slack_channel, getData(data.sub_channel!)); - // } - } - } else { - Application.log('User not found'); - } - } - } else { - throw Exception('Request Jira API Faile: $url'); - } - - return simpleFuture; - } -} diff --git a/refactoring/to-do.md b/refactoring/to-do.md new file mode 100644 index 0000000..4890060 --- /dev/null +++ b/refactoring/to-do.md @@ -0,0 +1,116 @@ +# 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 패턴 +**우선순위: 최고 / 난이도: 높음** + +2번 작업(util/ 정리)과 연계. `commands/` 구조 개편의 마무리 단계. +현재 새 커맨드 추가 시 두 곳을 수정해야 한다: +1. 도메인 데이터 파일 — 데이터 클래스 추가 +2. `commands/command.dart` — 130+ case factory switch에 추가 + +각 Command 클래스가 자신을 등록하는 패턴으로 변경하면 +새 커맨드 추가 시 해당 파일 하나만 수정하면 된다. + +작업 내용: +- `Command` 클래스에 `registerFactory()` static 메서드 추가 +- 각 커맨드 파일에 `static void register()` 메서드 추가 +- `application.dart` 또는 별도 registry 파일에서 모든 커맨드 등록 +- 기존 switch factory 제거 + +> ⚠️ 변경 범위가 크므로 2번 완료 후 진행 권장 + +--- + +### 4. 태그 시스템 중앙화 +**우선순위: 중간 / 난이도: 낮음** + +현재 태그 처리 로직(`` 등)이 `commands/command.dart` 내부에 +흩어져 있다. 별도 파일로 분리하면 AI가 "어떤 태그를 쓸 수 있나?" 질문에 +코드 한 곳만 읽어도 답할 수 있다. + +작업 내용: +- `lib/oto/core/tag_system.dart` 파일 생성 +- 태그 파싱/치환 로직을 `command.dart`에서 이동 +- 태그 종류 명세를 주석으로 정리 (`property`, `state`, `common` 등) +- `command.dart`는 `tag_system.dart`를 import해서 사용 + +--- + +### 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: "" +/// 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` 실행 포함) +- 태그 문법 레퍼런스 (``, ``, ``) +- 에러 핸들링 패턴 (`application.dart` catch 구조) +- 주의사항 (xcworkspace/xcodeproj `??` 패턴 등)