From c31eed3ed66e7372ca0845e80414cf4f5f864ced Mon Sep 17 00:00:00 2001 From: toki Date: Sat, 4 Apr 2026 19:08:49 +0900 Subject: [PATCH] Refactor: Update project structure and fix issues --- CLAUDE.md | 119 ++++++++++++++++++ lib/cli/commands/command_const.dart | 3 +- lib/cli/commands/command_install.dart | 2 +- .../commands/scheduler/scheduler_isolate.dart | 4 +- .../commands/scheduler/scheduler_manager.dart | 7 +- lib/cli/commands/scheduler/scheduler_osx.dart | 2 +- lib/oto/application.dart | 7 +- lib/oto/commands/build/build_ios.dart | 10 +- lib/oto/commands/build/fastlane_template.dart | 14 +-- lib/oto/commands/build/xcodeproj.dart | 6 +- lib/oto/commands/command.dart | 5 +- lib/oto/commands/ftp/ftp.dart | 22 +--- lib/oto/commands/ftp/upload.dart | 2 +- lib/oto/commands/git/git.dart | 18 +-- lib/oto/commands/jira/jira.dart | 2 +- lib/oto/commands/notification/slack.dart | 4 +- lib/oto/commands/web/web.dart | 2 +- lib/oto/core/data_composer.dart | 2 +- lib/oto/core/defined_data.dart | 8 +- lib/oto/core/tag_system.dart | 50 ++++---- lib/oto/data/base_data.dart | 22 ++++ lib/oto/data/base_data.g.dart | 30 +++++ lib/oto/data/build_data.dart | 8 +- lib/oto/data/jenkins_data.dart | 24 ---- lib/oto/data/jenkins_data.g.dart | 37 ------ lib/oto/data/notification_data.dart | 6 +- lib/oto/data/pipeline_data.dart | 3 - lib/oto/data/util_data.dart | 10 +- pubspec.yaml | 3 +- 29 files changed, 259 insertions(+), 173 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 lib/oto/data/jenkins_data.dart delete mode 100644 lib/oto/data/jenkins_data.g.dart diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d34cf50 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,119 @@ +# OTO CLI – Guide for AI Assistants + +## Project Overview + +OTO CLI is a Dart command-line tool that executes build/deploy pipelines defined in YAML files. +It integrates with Jenkins, runs local test builds, and supports a scheduler mode. + +## Execution Flow + +``` +bin/main.dart + └── Application (singleton) + └── DataComposer ← parses YAML + Jenkins env + └── Pipeline + └── Command.byType(CommandType) → Command.execute(DataCommand) +``` + +1. **`bin/main.dart`** – Entry point. Registers three CLI commands: `template`, `exe`, `scheduler`. +2. **`lib/cli/commands/command_exe.dart`** – Parses CLI flags (`-j` Jenkins, `-t` test, `-f` file path) and calls `Application.build()`. +3. **`lib/oto/application.dart`** – Singleton. Owns `property` map, `commonData`, `commandStates`. Calls `registerAllCommands()` then runs the pipeline. +4. **`lib/oto/core/data_composer.dart`** – Reads YAML and Jenkins env into structured data. +5. **`lib/oto/pipeline/pipeline.dart`** – Iterates `DataCommand` list and dispatches to `Command.byType()`. +6. **`lib/oto/commands/command_registry.dart`** – Single place where all `CommandType → Command` mappings are registered. + +## BuildType Enum + +Defined in `lib/oto/application.dart`: + +| Value | Description | +|-------|-------------| +| `jenkins` | CI mode – reads workspace and env from Jenkins environment variables | +| `test` | Local test mode – uses hardcoded test data | +| `file` | Reads pipeline YAML from a local file path | +| `scheduler` | Scheduler daemon mode | + +## Tag System (`lib/oto/core/tag_system.dart`) + +Tags are resolved inside YAML values at runtime. + +### Read tag: `` +Substitutes a value from the runtime store. + +```yaml +param: + workspace: # flat property access + version: # nested property access + state: # command state (ready/progress/complete) +``` + +- If the tag occupies the **entire** string, the original type is returned (e.g. a List stays a List). +- If the tag is **embedded** in a string, it is converted via `toString()`. + +### Write tag: `<@namespace.key>` +Stores the command result back into a property. + +```yaml +param: + set-version: <@property.appVersion> # saves result into property["appVersion"] +``` + +Used via `Command.setProperty()` / `TagSystem.setPropertyValue()`. + +## Adding a New Command + +1. **Define data model** in the appropriate `lib/oto/data/*_data.dart` file, extending `DataParam`. +2. **Add enum value** to `CommandType` in `lib/oto/commands/command.dart`. +3. **Implement command class** extending `Command` with `execute(DataCommand)`. +4. **Register** in `lib/oto/commands/command_registry.dart`. +5. Run `dart run build_runner build` if you added a new `@JsonSerializable` class. + +## Key Directory Structure + +``` +lib/ +├── bin/main.dart # entry point +├── cli/commands/ # CLI-layer commands (exe, template, scheduler) +└── oto/ + ├── application.dart # singleton orchestrator, BuildType enum + ├── commands/ + │ ├── command.dart # Command base class, CommandType enum + │ ├── command_registry.dart # all command registrations + │ ├── build/ # iOS/Flutter/Dart/MSBuild build commands + │ ├── file/ # file operations (copy, rename, delete, zip…) + │ ├── git/ # git commands + │ ├── infra/ # FTP, SMB + │ ├── notification/ # Slack, Mattermost + │ ├── process/ # process management + │ └── util/ # timers, JSON, string utils + ├── core/ + │ ├── tag_system.dart # tag substitution engine + │ ├── data_composer.dart # YAML + env → structured data + │ └── defined_data.dart # built-in YAML templates + ├── data/ + │ ├── base_data.dart # DataParam (base), DataCommon (Jenkins env) + │ ├── command_data.dart # DataCommand (re-exports all data types) + │ └── *_data.dart # domain-specific data models + └── pipeline/ + └── pipeline.dart # command dispatch loop +``` + +## Data Model Conventions + +- All command parameters extend `DataParam` (`lib/oto/data/base_data.dart`). +- JSON serialization uses `json_annotation`. Generated files are `*.g.dart`. +- `*.g.dart` files are normally generated by `dart run build_runner build` but can be edited directly when needed. +- `DataCommon` holds Jenkins environment data (workspace, job name, build number, etc.). +- `DataCommand` (in `command_data.dart`) is the unified container passed to every `Command.execute()`. + +## Error Handling + +- `Application.build()` in `application.dart` uses `catch (e, stacktrace)` (not `on Exception`) to also catch `Error` subtypes such as `TypeError`. +- On error, `exit(10)` is called to signal failure to the parent process. + +## Workspace Resolution (Command base class) + +`getWorkspace()` in `command.dart` resolves in this order: +1. `workspace` field on the command's data model +2. `property['workspace']` in the global property map +3. `commonData.workspace` (Jenkins environment) diff --git a/lib/cli/commands/command_const.dart b/lib/cli/commands/command_const.dart index b64b712..1661f0f 100644 --- a/lib/cli/commands/command_const.dart +++ b/lib/cli/commands/command_const.dart @@ -4,7 +4,7 @@ import 'package:oto_cli/cli/cli.dart'; class CommandConstant { static _CommandConstantBase? _instance; - // ignore: library_private_types_in_public_api + // ignore: library_private_types_in_public_api, non_constant_identifier_names static _CommandConstantBase? get OS { if (_instance == null) { if (Platform.isMacOS) { @@ -21,7 +21,6 @@ class CommandConstant { class _CommandConstantBase { String get installPath { - var userPath = Platform.environment['HOME']; return ''; } diff --git a/lib/cli/commands/command_install.dart b/lib/cli/commands/command_install.dart index a74b427..b0051d5 100644 --- a/lib/cli/commands/command_install.dart +++ b/lib/cli/commands/command_install.dart @@ -48,7 +48,7 @@ class CommandInstall extends CommandBase { } Future installFiles(String installPath) async { - var file = await File('$installPath/silentbatch.exe').create(); + await File('$installPath/silentbatch.exe').create(); // await file.writeAsBytes(silentbatch); return simpleFuture; } diff --git a/lib/cli/commands/scheduler/scheduler_isolate.dart b/lib/cli/commands/scheduler/scheduler_isolate.dart index 7327c49..f7f4764 100644 --- a/lib/cli/commands/scheduler/scheduler_isolate.dart +++ b/lib/cli/commands/scheduler/scheduler_isolate.dart @@ -49,7 +49,7 @@ class IsolateScheduler extends IsolateBase { if (hash != fileHash) { updated = true; fileHash = hash; - //파일 내용이 바뀔때 마다 새로 읽어 들임 + // Reload file contents on every change var map = Application.getMapFromYamlA(await file.readAsString()); //============ Validate yaml format ============// @@ -113,7 +113,7 @@ class IsolateScheduler extends IsolateBase { } } } else { - //file이 없을시 종료 + // Exit if the file no longer exists onError("The file '${file.path}' does not exist."); if (cron != null) await cron?.close(); return; diff --git a/lib/cli/commands/scheduler/scheduler_manager.dart b/lib/cli/commands/scheduler/scheduler_manager.dart index b18fc93..36dbcc8 100644 --- a/lib/cli/commands/scheduler/scheduler_manager.dart +++ b/lib/cli/commands/scheduler/scheduler_manager.dart @@ -226,7 +226,7 @@ class SchedulerManager { } Future _startProcess() async { - //Launchd로 자동 실행이 되지 않은경우 직접실행 + // Start directly if Launchd did not auto-launch the process if (_currentScheduler == null) { if (Platform.isWindows) { final program = Platform.resolvedExecutable; @@ -305,7 +305,7 @@ class SchedulerManager { Future start() async { _log('Scheduler is running on pid [$pid].'); while (true) { - //매초 마다 등록된 파일이 있나 체크 + // Check every second for registered scheduler files await Future.delayed(Duration(seconds: 1)); var settings = _localData.map; for (var alias in _schedulers) { @@ -373,7 +373,4 @@ class SchedulerManager { mode: FileMode.append, flush: true); } - void _logError(Exception e, StackTrace stacktace) { - _log('[${e.toString()}]\n${stacktace.toString()}'); - } } diff --git a/lib/cli/commands/scheduler/scheduler_osx.dart b/lib/cli/commands/scheduler/scheduler_osx.dart index f3e16e6..edcd0bb 100644 --- a/lib/cli/commands/scheduler/scheduler_osx.dart +++ b/lib/cli/commands/scheduler/scheduler_osx.dart @@ -9,7 +9,7 @@ class SchedulerOsx extends SchedulerManager { @override String get exePath { var exe = Platform.resolvedExecutable; - //homebrew로 설치시 예외 처리 + // Handle homebrew-installed executable path if (exe.startsWith('/usr/local')) { //intel mac exe = '/usr/local/bin/${path.basenameWithoutExtension(exe)}'; diff --git a/lib/oto/application.dart b/lib/oto/application.dart index 9d01974..5f89bfd 100644 --- a/lib/oto/application.dart +++ b/lib/oto/application.dart @@ -13,10 +13,9 @@ import 'package:oto_cli/oto/commands/command_registry.dart'; import 'package:oto_cli/oto/core/defined_data.dart'; import 'package:oto_cli/oto/pipeline/pipeline.dart'; import 'package:oto_cli/oto/data/command_data.dart'; -import 'package:oto_cli/oto/data/jenkins_data.dart'; import 'package:oto_cli/oto/core/data_composer.dart'; import 'package:yaml/yaml.dart'; -import 'package:path/path.dart' as pathlib; +import 'package:path/path.dart' as path; enum BuildType { jenkins, test, file, scheduler } @@ -30,7 +29,7 @@ class Application { if (Application.instance.buildType == BuildType.jenkins) { return Platform.environment['WORKSPACE']!; } else { - return pathlib.current; + return path.current; } } @@ -96,7 +95,7 @@ class Application { } property = Command.replaceAllTagsMap(property, replace: true); - //set command in dic, 중복 커맨드 id 필터 + // Register commands; filter out duplicate command IDs for (var command in build.commands) { if (dataCommandMap.containsKey(command.id)) { var ex = ExceptionData(); diff --git a/lib/oto/commands/build/build_ios.dart b/lib/oto/commands/build/build_ios.dart index d14409b..008ac5c 100644 --- a/lib/oto/commands/build/build_ios.dart +++ b/lib/oto/commands/build/build_ios.dart @@ -127,7 +127,7 @@ class BuildiOS extends Command { } String? target = data.target; - //version이 있을경우 세팅, 없을경우 기존 버전값을 메모리에 가지고 있음 + // If version is provided, apply it; otherwise retain the existing value from the project if (version != null && version.isNotEmpty) { await updateMarketingVersion( '${data.workspace}/$xcodeproj/project.pbxproj', version); @@ -136,7 +136,7 @@ class BuildiOS extends Command { version = await getSettingXcodeproj( data.workspace!, xcodeproj!, target!, 'MARKETING_VERSION'); } - //buildNumber가 있을경우 세팅, 없을경우 기존 버전값을 메모리에 가지고 있음 + // If buildNumber is provided, apply it; otherwise retain the existing value from the project if (buildNumber != null && buildNumber.isNotEmpty) { await updateBuildNumber( '${data.workspace}/$xcodeproj/project.pbxproj', buildNumber); @@ -230,7 +230,7 @@ class BuildiOS extends Command { static Future initializeFastlane( String workspace, String apiKeyPath, String appID) async { var json = jsonDecode(File(apiKeyPath).readAsStringSync()); - var fastlaneContent = fastlaneTemplete; + var fastlaneContent = fastlaneTemplate; fastlaneContent = fastlaneContent .replaceFirst('[APP_IDENTIFIER]', appID) .replaceFirst('[API_KEY_ID]', json['key_id']) @@ -283,7 +283,7 @@ class BuildiOS extends Command { final fileRef = document.findAllElements('FileRef').firstWhere( (element) => element.getAttribute('location')?.endsWith('.xcodeproj') ?? false, - orElse: () => XmlElement(XmlName('Dummy')), // 타입만 맞추고 null 체크는 나중에 + orElse: () => XmlElement(XmlName('Dummy')), // sentinel for null-check below ); final location = @@ -338,7 +338,7 @@ class BuildiOS extends Command { } if (insideBuildSettings && trimmed == '};') { - // 삽입 필요 시 추가 + // Insert the setting if not already present if (!hasTargetSetting) { updatedLines.add(' $settingKey = $settingValue;'); } diff --git a/lib/oto/commands/build/fastlane_template.dart b/lib/oto/commands/build/fastlane_template.dart index e655080..02d7d10 100644 --- a/lib/oto/commands/build/fastlane_template.dart +++ b/lib/oto/commands/build/fastlane_template.dart @@ -1,4 +1,4 @@ -var fastlaneTemplete = ''' +var fastlaneTemplate = ''' default_platform(:ios) APP_IDENTIFIER = "[APP_IDENTIFIER]" @@ -13,7 +13,7 @@ platform :ios do end - desc "지정한 앱 버전 + 빌드 넘버에 대한 TestFlight 상태 추적 (JSON 출력)" + desc "Track TestFlight status for a specific app version and build number (JSON output)" lane :testflight_status do require 'spaceship' require 'json' @@ -43,7 +43,7 @@ platform :ios do end - desc "TestFlight에 등록된 특정 버전의 최신 빌드 넘버 가져오기 (JSON 출력)" + desc "Get the latest build number for a specific version registered on TestFlight (JSON output)" lane :latest_build_number do require 'json' @@ -67,7 +67,7 @@ platform :ios do end - desc "필터 조건에 맞춰 App Store Connect 버전 상태 JSON 출력" + desc "Output App Store Connect version status as JSON based on filter conditions" lane :appstore_versions do require 'spaceship' require 'json' @@ -81,19 +81,19 @@ platform :ios do .select { |v| v.platform == "IOS" } .reject { |v| v.app_store_state == "REPLACED_WITH_NEW_VERSION" } - # 1. READY_FOR_SALE 중 최신 + # 1. Latest among READY_FOR_SALE latest_ready = versions .select { |v| v.app_store_state == "READY_FOR_SALE" } .max_by { |v| v.respond_to?(:created_date) ? v.created_date.to_s : "" } - # 2. PREPARE_FOR_SUBMISSION 중 가장 버전 낮은 것 + # 2. Lowest version among PREPARE_FOR_SUBMISSION prepare_versions = versions .select { |v| v.app_store_state == "PREPARE_FOR_SUBMISSION" } .sort_by { |v| Gem::Version.new(v.version_string) } lowest_prepare = prepare_versions.first - # 3. 기타 상태 버전들 (READY_FOR_SALE, PREPARE_FOR_SUBMISSION, REPLACED 제외) + # 3. Other status versions (excluding READY_FOR_SALE, PREPARE_FOR_SUBMISSION, REPLACED) included_states = ["READY_FOR_SALE", "PREPARE_FOR_SUBMISSION", "REPLACED_WITH_NEW_VERSION"] others = versions.reject { |v| included_states.include?(v.app_store_state) } diff --git a/lib/oto/commands/build/xcodeproj.dart b/lib/oto/commands/build/xcodeproj.dart index 928c48b..6e24346 100644 --- a/lib/oto/commands/build/xcodeproj.dart +++ b/lib/oto/commands/build/xcodeproj.dart @@ -17,15 +17,15 @@ target = project.targets.find { |t| t.name == '{TARGET}' } group = project.main_group file_ref = group.new_file('{ADD_FILE_PATH}') -# 📌 타겟에 파일 포함 +# Add file to target build_file = target.add_file_references([file_ref]).first -# 📌 Copy Bundle Resources에 명시적으로 추가 +# Explicitly add to Copy Bundle Resources target.resources_build_phase.add_file_reference(file_ref) project.save -puts "✅ {ADD_FILE_PATH} 파일이 프로젝트에 추가되고, Copy Bundle Resources에 포함되었습니다." +puts "✅ {ADD_FILE_PATH} has been added to the project and included in Copy Bundle Resources." '''; /* diff --git a/lib/oto/commands/command.dart b/lib/oto/commands/command.dart index 315a305..a8d87f7 100644 --- a/lib/oto/commands/command.dart +++ b/lib/oto/commands/command.dart @@ -7,7 +7,6 @@ import 'package:dart_framework/utils/system_util.dart'; import 'package:oto_cli/oto/application.dart'; import 'package:oto_cli/oto/core/tag_system.dart'; import 'package:oto_cli/oto/data/command_data.dart'; -import 'package:oto_cli/oto/data/jenkins_data.dart'; import 'package:oto_cli/oto/pipeline/pipeline_exe_handle.dart'; enum CommandType { @@ -148,11 +147,11 @@ abstract class Command { String getWorkspace(String? dataWorkspace) { if (dataWorkspace == null) { - //모델에 정의된 workspace가 없을경우 property에 설정된 workspace 우선 + // No workspace on model: prefer workspace from property if (property.containsKey('workspace')) { return getAbs(property['workspace']); } - //property에도 설정이 되어 있지 않으면 현재 실행 프로젝트 기준으로 workspace 설정 + // No workspace in property either: fall back to the current project root else { return getAbs(commonData!.workspace); } diff --git a/lib/oto/commands/ftp/ftp.dart b/lib/oto/commands/ftp/ftp.dart index 24aa909..41ba899 100644 --- a/lib/oto/commands/ftp/ftp.dart +++ b/lib/oto/commands/ftp/ftp.dart @@ -10,11 +10,9 @@ import 'package:oto_cli/oto/commands/command.dart'; class FTP { final String _host; final int _port; - final int _timeout; final String _user; final String _pass; - bool _isProgress = false; String? _currentRemoteDir; FTPConnect? _ftp; @@ -26,8 +24,7 @@ class FTP { : _host = host, _port = port, _user = user, - _pass = password, - _timeout = timeout { + _pass = password { _ftp = FTPConnect(host, port: port, user: user, pass: password, timeout: timeout); } @@ -59,7 +56,6 @@ class FTP { Map map = {}; _currentRemoteDir = null; try { - _isProgress = true; var entries = localRemoteMap.entries; for (var entry in entries) { var local = Path.getNormalizePath(entry.key); @@ -79,9 +75,7 @@ class FTP { await _upload(local, remote); } } - } finally { - _isProgress = false; - } + } finally {} } Future _upload(String local, String remote) async { @@ -112,13 +106,11 @@ class FTP { var c = Completer(); _cache = >{}; try { - _isProgress = true; var entries = remoteLocalMap.entries; for (var entry in entries) { await _download(entry.key, entry.value); } } finally { - _isProgress = false; c.complete(); } return c.future; @@ -164,7 +156,6 @@ class FTP { try { await _ftp!.changeDirectory(path); var list = await _ftp!.listDirectoryContent(); - var count = 0; for (var item in list) { FileType? type; switch (item.type) { @@ -175,15 +166,11 @@ class FTP { type = FileType.file; break; case FTPEntryType.link: - // TODO: Handle this case. - break; case FTPEntryType.unknown: - // TODO: Handle this case. break; } var name = item.name; if (name != '.' && name != '..') { - count++; var remotePath = '$path/$name'; map[remotePath] = type!; // Application.log('$count = $type = $remotePath'); @@ -199,7 +186,6 @@ class FTP { Map? map; _currentRemoteDir = null; try { - _isProgress = true; for (var item in remoteList) { var parent = Path.getParent(item); var name = Path.getName(item); @@ -222,9 +208,7 @@ class FTP { Application.log('Deleted: $item'); } } - } finally { - _isProgress = false; - } + } finally {} } Future createFolder(String path) async { diff --git a/lib/oto/commands/ftp/upload.dart b/lib/oto/commands/ftp/upload.dart index 0dc257b..f85cc59 100644 --- a/lib/oto/commands/ftp/upload.dart +++ b/lib/oto/commands/ftp/upload.dart @@ -40,7 +40,7 @@ class Upload extends Command { var nameLocal = Path.getName(local); if (nameLocal == '*') { - //에스테리크(*) 일경우 리모트 폴더를 삭제해서 전체 교체 + // Wildcard (*): delete the remote folder first for a full replacement //Remove before folder if (remoteMap.containsKey(remote) && remoteMap[remote] == FileType.directory) { diff --git a/lib/oto/commands/git/git.dart b/lib/oto/commands/git/git.dart index 8f07feb..2703d22 100644 --- a/lib/oto/commands/git/git.dart +++ b/lib/oto/commands/git/git.dart @@ -25,7 +25,7 @@ class Git extends Command { } } -//무시가가능한 에러 무시 +// Ignorable errors are suppressed class GitCommit extends Command { @override Future execute(DataCommand command) async { @@ -50,11 +50,11 @@ class GitCommit extends Command { class GitPull extends Command { @override Future execute(DataCommand command) async { - var data = DataGitPush.fromJson(getParam(command)); + var data = DataGitPull.fromJson(getParam(command)); var shell = StringBuffer(getCDPath(workspace)); shell.write( - ' $and git switch -c ${data.branch!} || git switch ${data.branch!}'); + ' $and git switch -c ${data.branch} || git switch ${data.branch}'); shell.write(' $and git pull origin ${data.branch}'); var process = @@ -64,7 +64,7 @@ class GitPull extends Command { } } -//무시가가능한 에러 무시 +// Ignorable errors are suppressed class GitPush extends Command { @override Future execute(DataCommand command) async { @@ -86,7 +86,7 @@ class GitPush extends Command { } } -//로컬에 체크아웃이 없을시 체크아웃하고, 체크아웃이 있을시 이동, 원할시 pull수행 +// Checks out branch if not present locally; switches to it if it exists; optionally pulls class GitCheckout extends Git { @override Future execute(DataCommand command) async { @@ -122,7 +122,7 @@ class GitCheckout extends Git { } } -//Git commit에 대한 count 값을 리턴 해준다 +// Returns the total commit count class GitCount extends Command { @override Future execute(DataCommand command) async { @@ -139,7 +139,7 @@ class GitCount extends Command { } } -//git의 리비전에 대해 돌려준다 +// Returns the git revision hash class GitRev extends Command { @override Future execute(DataCommand command) async { @@ -158,7 +158,7 @@ class GitRev extends Command { } } -//git 초기화 +// Resets git working tree to a clean state class GitReset extends Command { @override Future execute(DataCommand command) async { @@ -243,7 +243,7 @@ class GitStashApply extends Command { } } -//무시가가능한 에러 무시 +// Ignorable errors are suppressed class GitBranch extends Command { @override Future execute(DataCommand command) async { diff --git a/lib/oto/commands/jira/jira.dart b/lib/oto/commands/jira/jira.dart index dda7a33..1f97c92 100644 --- a/lib/oto/commands/jira/jira.dart +++ b/lib/oto/commands/jira/jira.dart @@ -77,7 +77,7 @@ class Jira extends Command { slackData.blocks = []; var blockSection = SlackBlockSection(); blockSection.text = SlackBlockText(); - blockSection.text!.text = ':jira: 새 Jira 업무가 할당되었습니다.'; + blockSection.text!.text = ':jira: A new Jira task has been assigned.'; slackData.blocks!.add(blockSection); attachment.blocks = []; diff --git a/lib/oto/commands/notification/slack.dart b/lib/oto/commands/notification/slack.dart index 77a8ff5..74922ae 100644 --- a/lib/oto/commands/notification/slack.dart +++ b/lib/oto/commands/notification/slack.dart @@ -10,7 +10,7 @@ import 'package:oto_cli/oto/commands/command.dart'; import 'package:oto_cli/oto/data/command_data.dart'; import 'package:color/color.dart'; import 'package:dart_framework/utils/slack/slack_sender.dart'; -import 'package:dart_framework/utils/slack/slack_data.dart' as slackData; +import 'package:dart_framework/utils/slack/slack_data.dart' as slack_data; class Slack extends Command { @override @@ -53,7 +53,7 @@ class SlackFile extends Command { @override Future execute(DataCommand command) async { var data = DataSlackFile.fromJson(getParam(command)); - var slackFile = slackData.SlackFile(); + var slackFile = slack_data.SlackFile(); slackFile.title = data.title; slackFile.initial_comment = data.initialComment; slackFile.file = File(data.filePath); diff --git a/lib/oto/commands/web/web.dart b/lib/oto/commands/web/web.dart index ffe17b4..811e05c 100644 --- a/lib/oto/commands/web/web.dart +++ b/lib/oto/commands/web/web.dart @@ -5,7 +5,7 @@ import 'package:oto_cli/oto/commands/command.dart'; import 'package:oto_cli/oto/data/command_data.dart'; import 'dart:io'; import 'package:http/http.dart' as http; -// ignore: depend_on_referenced_packages +// ignore: depend_on_referenced_packages, implementation_imports import 'package:http_parser/src/media_type.dart'; class WebBase extends Command { diff --git a/lib/oto/core/data_composer.dart b/lib/oto/core/data_composer.dart index a5b3ea8..9776517 100644 --- a/lib/oto/core/data_composer.dart +++ b/lib/oto/core/data_composer.dart @@ -6,7 +6,7 @@ import 'package:dart_framework/platform/process.dart'; import 'package:dart_framework/utils/system_util.dart'; import 'package:oto_cli/cli/cli.dart'; import 'package:oto_cli/oto/application.dart'; -import 'package:oto_cli/oto/data/jenkins_data.dart'; +import 'package:oto_cli/oto/data/base_data.dart'; import 'package:oto_cli/oto/core/defined_data.dart'; abstract class DataComposer { diff --git a/lib/oto/core/defined_data.dart b/lib/oto/core/defined_data.dart index 5bd6109..49df68d 100644 --- a/lib/oto/core/defined_data.dart +++ b/lib/oto/core/defined_data.dart @@ -155,11 +155,11 @@ commands: # token: bot-token # channelId: n78a1wrdwjydzdoat6gci8cphe # message: -# message: 봇 테스트 메시지입니다. +# message: Bot test message. # props: # attachments: # - pretext: Mattermost Bot Test -# text: 정상적으로 전송되면 이 메시지가 채널에 표시됩니다. +# text: If sent successfully, this message will appear in the channel. '''; } else if (Platform.isWindows) { @@ -287,11 +287,11 @@ commands: # token: bot-token # channelId: n78a1wrdwjydzdoat6gci8cphe # message: -# message: 봇 테스트 메시지입니다. +# message: Bot test message. # props: # attachments: # - pretext: Mattermost Bot Test -# text: 정상적으로 전송되면 이 메시지가 채널에 표시됩니다. +# text: If sent successfully, this message will appear in the channel. '''; } else if (Platform.isLinux) {} diff --git a/lib/oto/core/tag_system.dart b/lib/oto/core/tag_system.dart index 2ba6728..8fd960d 100644 --- a/lib/oto/core/tag_system.dart +++ b/lib/oto/core/tag_system.dart @@ -4,42 +4,42 @@ import 'dart:io'; import 'package:dart_framework/utils/path.dart'; import 'package:oto_cli/oto/application.dart'; -/// OTO 태그 시스템 +/// OTO Tag System /// -/// YAML 파이프라인에서 동적 값을 참조하기 위한 태그 문법을 처리한다. +/// Handles the tag syntax used in YAML pipelines to reference dynamic values. /// -/// ## 읽기 태그 (값 치환) +/// ## Read tags (value substitution) /// /// ```yaml /// exe: BuildiOS /// param: -/// workspace: # property 직접 접근 -/// version: # property 중첩 접근 -/// state: # commandStates 접근 (ready/progress/complete) +/// workspace: # direct property access +/// version: # nested property access +/// state: # commandStates access (ready/progress/complete) /// ``` /// -/// 태그가 문자열 전체인 경우 원본 타입 그대로 반환 (String→Any). -/// 문자열 일부인 경우 toString()으로 삽입. +/// When a tag occupies the entire string, the original type is returned (String→Any). +/// When a tag is embedded within a string, it is inserted via toString(). /// -/// ## 쓰기 태그 (값 저장) +/// ## Write tags (value storage) /// /// ```yaml /// exe: SetValue /// param: -/// set-version: <@property.appVersion> # property에 값 저장 +/// set-version: <@property.appVersion> # store value into property /// ``` /// -/// `setPropertyValue()` 또는 `setProperty()` 호출 시 사용. +/// Used when calling `setPropertyValue()` or `setProperty()`. class TagSystem { - /// 읽기 태그 패턴: `` + /// Read tag pattern: `` static RegExp regEx = RegExp(r'()'); - /// 쓰기 태그 패턴: `<@namespace.key>` + /// Write tag pattern: `<@namespace.key>` static RegExp regSetEx = RegExp(r'(<@)([_a-zA-Z0-9.-]+)(>)'); - /// Map의 모든 키/값에서 태그를 재귀적으로 치환한다. + /// Recursively replaces tags in all keys and values of a Map. /// - /// [replace]가 true이면 원본 [map]을 직접 수정하고 반환한다. + /// If [replace] is true, modifies and returns the original [map] in place. static Map replaceAllTagsMap(Map map, {bool replace = false}) { var newMap = {}; @@ -73,7 +73,7 @@ class TagSystem { return newMap; } - /// List의 모든 요소에서 태그를 재귀적으로 치환한다. + /// Recursively replaces tags in all elements of a List. static List replaceAllTagsList(List list, {bool replace = false}) { var newList = []; for (var i = 0; i < list.length; ++i) { @@ -96,7 +96,7 @@ class TagSystem { return newList; } - /// 쓰기 태그(`<@property.key>`)로 지정된 property에 값을 저장한다. + /// Stores a value into the property specified by a write tag (`<@property.key>`). static Future setPropertyValue(String tag, dynamic value, {bool? showPrint}) async { var match = regSetEx.firstMatch(tag); @@ -125,10 +125,10 @@ class TagSystem { return Future.value(); } - /// 태그 이름으로 실제 값을 조회한다. + /// Looks up the actual value for a tag name. /// /// - `property.key` → `Application.instance.property[key]` - /// - `property.nested.key` → 중첩 Map 순회 + /// - `property.nested.key` → traverses nested Maps /// - `state.commandId` → `Application.instance.commandStates[commandId]` static dynamic _getTagValue(String value) { var setted = false; @@ -183,7 +183,7 @@ class TagSystem { return data; } - /// 상대경로(`.`, `..`)를 절대경로로 변환한다. 경로 내 태그도 치환된다. + /// Converts a relative path (`.`, `..`) to an absolute path, also resolving any tags within it. static String getAbs(String path) { path = getPathNormal(path); if (path == '.' || path.startsWith('./') || path.startsWith('.\\')) { @@ -196,15 +196,15 @@ class TagSystem { return File(path).absolute.path; } - /// 경로 구분자를 정규화하고 태그를 치환한다. + /// Normalizes path separators and resolves tags within the path. static String getPathNormal(String path) { return replaceTagValue(Path.getNormal(path)); } - /// 문자열에서 태그(``)를 치환한다. + /// Replaces tags (``) within a string. /// - /// 태그가 문자열 전체인 경우 원본 타입(dynamic)을 반환한다. - /// 태그가 문자열 일부인 경우 문자열로 삽입한다. + /// If the tag occupies the entire string, returns the original type (dynamic). + /// If the tag is embedded within a string, it is inserted as a string. static dynamic replaceTagValue(String raw) { var list = regEx.allMatches(raw); if (list.length == 1) { @@ -223,7 +223,7 @@ class TagSystem { return raw; } - /// 태그 하나만 포함된 문자열에서 값을 원본 타입으로 반환한다. + /// Returns the value in its original type from a string that contains exactly one tag. static dynamic replaceSingleTagValue(String raw) { dynamic value; var list = regEx.allMatches(raw); diff --git a/lib/oto/data/base_data.dart b/lib/oto/data/base_data.dart index 4d36ce6..6c6aeae 100644 --- a/lib/oto/data/base_data.dart +++ b/lib/oto/data/base_data.dart @@ -4,6 +4,28 @@ import 'package:json_annotation/json_annotation.dart'; part 'base_data.g.dart'; +@JsonSerializable() +class DataCommon { + late String workspace; + late String jenkinsHome; + late String jenkinsUrl; + late String buildUrl; + late String jobUrl; + late String jobName; + late String gitCommit; + late int buildNumber; + late int buildID; + late String buildDisplayName; + late String gitBranch; + late String buildTag; + + DataCommon(); + + factory DataCommon.fromJson(Map json) => + _$DataCommonFromJson(json); + Map toJson() => _$DataCommonToJson(this); +} + @JsonSerializable() class DataParam { DataParam(); diff --git a/lib/oto/data/base_data.g.dart b/lib/oto/data/base_data.g.dart index 500cf35..2bbaa00 100644 --- a/lib/oto/data/base_data.g.dart +++ b/lib/oto/data/base_data.g.dart @@ -6,6 +6,36 @@ part of 'base_data.dart'; // JsonSerializableGenerator // ************************************************************************** +DataCommon _$DataCommonFromJson(Map json) => DataCommon() + ..workspace = json['workspace'] as String + ..jenkinsHome = json['jenkinsHome'] as String + ..jenkinsUrl = json['jenkinsUrl'] as String + ..buildUrl = json['buildUrl'] as String + ..jobUrl = json['jobUrl'] as String + ..jobName = json['jobName'] as String + ..gitCommit = json['gitCommit'] as String + ..buildNumber = (json['buildNumber'] as num).toInt() + ..buildID = (json['buildID'] as num).toInt() + ..buildDisplayName = json['buildDisplayName'] as String + ..gitBranch = json['gitBranch'] as String + ..buildTag = json['buildTag'] as String; + +Map _$DataCommonToJson(DataCommon instance) => + { + 'workspace': instance.workspace, + 'jenkinsHome': instance.jenkinsHome, + 'jenkinsUrl': instance.jenkinsUrl, + 'buildUrl': instance.buildUrl, + 'jobUrl': instance.jobUrl, + 'jobName': instance.jobName, + 'gitCommit': instance.gitCommit, + 'buildNumber': instance.buildNumber, + 'buildID': instance.buildID, + 'buildDisplayName': instance.buildDisplayName, + 'gitBranch': instance.gitBranch, + 'buildTag': instance.buildTag, + }; + DataParam _$DataParamFromJson(Map json) => DataParam() ..workspace = json['workspace'] as String? ..passExitCodes = (json['passExitCodes'] as List?) diff --git a/lib/oto/data/build_data.dart b/lib/oto/data/build_data.dart index 789dfb8..6424b8b 100644 --- a/lib/oto/data/build_data.dart +++ b/lib/oto/data/build_data.dart @@ -126,11 +126,11 @@ class DataBuildiOS extends DataParam { late Map? settings; //Version Setting - late String? target; //없을시 xcodeproj 자동 검색하여 최상단 target을 가져옴 - late String? version; //존재시 autoBuildNumber = false - late String? buildNumber; //존재시 autoBuildNumber = false + late String? target; // if null, auto-detects the top-level target from xcodeproj + late String? version; // if set, disables autoBuildNumber + late String? buildNumber; // if set, disables autoBuildNumber late bool? autoBuildNumber = false; - late String? apiKeyPath; //autoVersionNumber 사용시 필수 + late String? apiKeyPath; // required when using autoVersionNumber //set late String? setVersion; diff --git a/lib/oto/data/jenkins_data.dart b/lib/oto/data/jenkins_data.dart deleted file mode 100644 index 4530f8f..0000000 --- a/lib/oto/data/jenkins_data.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -part 'jenkins_data.g.dart'; - -@JsonSerializable() -class DataCommon { - late String workspace; - late String jenkinsHome; - late String jenkinsUrl; - late String buildUrl; - late String jobUrl; - late String jobName; - late String gitCommit; - late int buildNumber; - late int buildID; - late String buildDisplayName; - late String gitBranch; - late String buildTag; - - DataCommon(); - - factory DataCommon.fromJson(Map json) => - _$DataCommonFromJson(json); - Map toJson() => _$DataCommonToJson(this); -} diff --git a/lib/oto/data/jenkins_data.g.dart b/lib/oto/data/jenkins_data.g.dart deleted file mode 100644 index 6d390dc..0000000 --- a/lib/oto/data/jenkins_data.g.dart +++ /dev/null @@ -1,37 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'jenkins_data.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -DataCommon _$DataCommonFromJson(Map json) => DataCommon() - ..workspace = json['workspace'] as String - ..jenkinsHome = json['jenkinsHome'] as String - ..jenkinsUrl = json['jenkinsUrl'] as String - ..buildUrl = json['buildUrl'] as String - ..jobUrl = json['jobUrl'] as String - ..jobName = json['jobName'] as String - ..gitCommit = json['gitCommit'] as String - ..buildNumber = (json['buildNumber'] as num).toInt() - ..buildID = (json['buildID'] as num).toInt() - ..buildDisplayName = json['buildDisplayName'] as String - ..gitBranch = json['gitBranch'] as String - ..buildTag = json['buildTag'] as String; - -Map _$DataCommonToJson(DataCommon instance) => - { - 'workspace': instance.workspace, - 'jenkinsHome': instance.jenkinsHome, - 'jenkinsUrl': instance.jenkinsUrl, - 'buildUrl': instance.buildUrl, - 'jobUrl': instance.jobUrl, - 'jobName': instance.jobName, - 'gitCommit': instance.gitCommit, - 'buildNumber': instance.buildNumber, - 'buildID': instance.buildID, - 'buildDisplayName': instance.buildDisplayName, - 'gitBranch': instance.gitBranch, - 'buildTag': instance.buildTag, - }; diff --git a/lib/oto/data/notification_data.dart b/lib/oto/data/notification_data.dart index 699f6ab..9329e4b 100644 --- a/lib/oto/data/notification_data.dart +++ b/lib/oto/data/notification_data.dart @@ -13,9 +13,9 @@ class DataSlackSender extends DataParam { DataSlackSender(); late String token; - late dynamic property; //oto 내부 생성된 object의 경우 - late Map? message; //yaml로 선언된 값 - late String? messageJson; //json값으로 선언된 값 + late dynamic property; // object created internally by oto + late Map? message; // value declared in YAML + late String? messageJson; // value declared as JSON string late Map? idMap; factory DataSlackSender.fromJson(Map json) => diff --git a/lib/oto/data/pipeline_data.dart b/lib/oto/data/pipeline_data.dart index 2e59cc9..6f503a0 100644 --- a/lib/oto/data/pipeline_data.dart +++ b/lib/oto/data/pipeline_data.dart @@ -99,7 +99,6 @@ class DataExeHandle implements DataValidator { factory DataExeHandle.fromJson(Map json) => _$DataExeHandleFromJson(json); - @override Map toJson() => _$DataExeHandleToJson(this); } @@ -162,7 +161,6 @@ class DataContain extends DataValidator { factory DataContain.fromJson(Map json) => _$DataContainFromJson(json); - @override Map toJson() => _$DataContainToJson(this); } @@ -225,7 +223,6 @@ class DataForeach extends DataValidator { factory DataForeach.fromJson(Map json) => _$DataForeachFromJson(json); - @override Map toJson() => _$DataForeachToJson(this); } diff --git a/lib/oto/data/util_data.dart b/lib/oto/data/util_data.dart index 333d359..a955691 100644 --- a/lib/oto/data/util_data.dart +++ b/lib/oto/data/util_data.dart @@ -103,11 +103,11 @@ class DataStringReplacePattern extends DataParam { DataStringReplacePattern(); int? startAt = 0; - late String startPattern; //시작 패턴 - late String endPattern; //종료 패턴 - late String text; //원본 텍스트 - late String value; //교체될 값 - late String setValue; //결과 저장소 + late String startPattern; // start delimiter pattern + late String endPattern; // end delimiter pattern + late String text; // original text to process + late String value; // replacement value + late String setValue; // property key to store the result factory DataStringReplacePattern.fromJson(Map json) => _$DataStringReplacePatternFromJson(json); diff --git a/pubspec.yaml b/pubspec.yaml index 5bf0714..4cdcee1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,8 @@ environment: # Add regular dependencies here. dependencies: - # path: ^1.8.0 + path: ^1.8.0 + http: ^1.2.0 json_annotation: ^4.8.1 resource_importer: ^0.2.0 dart_framework: