Compare commits

...

10 commits

26 changed files with 1531 additions and 80 deletions

10
.claude/settings.json Normal file
View file

@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Read(**)",
"Bash(bash agent-ops/bin/sync.sh agentic-framework)",
"Bash(git commit -m ' *)",
"Bash(grep \"\\\\.dart$\")"
]
}
}

16
.cursorrules Normal file
View file

@ -0,0 +1,16 @@
# 공통 규칙
- 기존 구조를 우선한다. 새 파일 생성보다 기존 파일 수정을 우선한다.
- 코드 변경 전 관련 domain rule을 먼저 확인한다.
- 요청 범위를 넘는 변경을 하지 않는다.
- 불확실하면 단정하지 말고 후보를 제시한다.
아래 요청은 `agent-ops/skills/common/router.md`를 읽고 수행한다.
- agent-ops 초기화
- domain rule 생성
- skill 생성
- git commit / git push
- agent-ops 업데이트 / 진입 파일 재적용
`agent-ops/rules/project/rules.md`와
`agent-ops/rules/private/rules.md`를 읽고 작업을 시작한다. 파일이 없을경우 무시한다.

1
.gitignore vendored
View file

@ -57,3 +57,4 @@ release/
assets/data/app_data.json
.DS_Store
agent-ops/rules/private/
.claude/settings.local.json

374
README.md
View file

@ -1,2 +1,372 @@
A sample command-line application with an entrypoint in `bin/`, library code
in `lib/`, and example unit test in `test/`.
# OTO CLI
YAML로 정의된 CI/CD 파이프라인을 실행하는 Dart 기반 자동화 도구.
Jenkins, FTP, Git, Slack/Mattermost, iOS/Flutter/Android 빌드 등 다양한 작업을 커맨드로 추상화하고, 파이프라인 엔진이 순차 실행한다.
---
## 설치
```bash
dart pub get
dart compile exe bin/main.dart -o oto
```
---
## 실행 모드
| 플래그 | 모드 | 설명 |
|--------|------|------|
| `-j` | `jenkins` | Jenkins 환경 변수에서 워크스페이스·YAML 읽기 |
| `-t` | `test` | 하드코딩된 테스트 데이터로 로컬 실행 |
| `-f <path>` | `file` | 지정한 YAML 파일 경로에서 파이프라인 실행 |
| (daemon) | `scheduler` | 스케줄러 데몬 모드 |
```bash
oto -j # Jenkins CI 모드
oto -t # 로컬 테스트 모드
oto -f ./build.yaml # 파일 기반 실행
```
---
## 실행 흐름
```
bin/main.dart
└── Application (singleton)
└── DataComposer ← YAML + Jenkins env 파싱
└── Pipeline
└── Command.byType(CommandType) → execute(DataCommand)
```
---
## YAML 파이프라인 구조
```yaml
property:
workspace: /path/to/project
version: 1.0.0
env: prod
commands:
- command: BuildiOS
id: buildApp
param:
workspacePath: <!property.workspace>/ios/Runner.xcworkspace
scheme: Runner
pipeline:
workflow:
- exe: buildApp
```
### 태그 시스템
| 태그 | 방향 | 설명 |
|------|------|------|
| `<!property.key>` | 읽기 | property에서 값 치환 |
| `<@property.key>` | 쓰기 | 커맨드 결과를 property에 저장 |
- 태그가 문자열 전체이면 원본 타입(List, Map 등) 유지
- 태그가 문자열 일부이면 `toString()` 변환
- 중첩 접근 지원: `<!property.build.version>`
---
## 파이프라인 흐름 제어
### exe — 단순 실행
```yaml
- exe: buildApp
```
### exe-handle — 성공/실패 분기
```yaml
- exe-handle:
id: uploadArtifact
on-success:
- exe: notifySuccess
on-fail:
- exe: notifyError
```
### if — 조건 분기
```yaml
- if:
condition-string: "<!property.env> == prod"
type: string
on-true:
- exe: deployProd
on-false:
- exe: deployDev
```
**지원 연산자:** `==`, `!=`, `<`, `<=`, `>`, `>=`
**지원 타입:** `string`, `int`, `float`, `bool`, `date`, `version`, `object`
### foreach — 반복
```yaml
- foreach:
iterator: <!property.platforms> # List 또는 Map
setValue: <@property.target>
on-do:
- exe: buildTarget
```
### while — 조건 루프
```yaml
- while:
condition-string: "<!property.retry> < 3"
type: int
on-do:
- exe: checkStatus
```
### switch — 멀티 케이스
```yaml
- switch:
value: <!property.env>
cases:
prod:
- exe: deployProd
dev:
- exe: deployDev
```
### wait-until — 대기
```yaml
- wait-until-true:
condition-string: "<!state.buildStep> == complete"
interval: 10
```
### async — 비동기 실행
```yaml
- async: notifyStart # 완료를 기다리지 않고 다음 단계 진행
```
---
## 커맨드 목록
### 빌드
| 커맨드 | 설명 |
|--------|------|
| `BuildiOS` | xcodebuild로 iOS 앱 빌드 |
| `ArchiveiOS` | xcodebuild archive |
| `ExportiOS` | xcodebuild -exportArchive |
| `PublishiOS` | App Store Connect 배포 |
| `TestflightUpload` | TestFlight 업로드 |
| `TestflightStatusCheck` | TestFlight 처리 상태 확인 |
| `TestflightDistribute` | TestFlight 그룹 배포 |
| `BuildFlutter` | flutter build |
| `BuildDart` | dart build |
| `BuildDartCompile` | dart compile |
| `BuildMSBuild` | MSBuild (.NET) |
| `ProductBuild` | macOS productbuild |
| `Notarize` | macOS 공증 |
### 파일
| 커맨드 | 설명 |
|--------|------|
| `Copy` | 파일/디렉토리 복사 (이동 포함) |
| `Delete` | 파일/디렉토리 삭제 |
| `Rename` | 이름 변경 |
| `FileRead` | 파일 읽기 → property 저장 |
| `FileWrite` | property 값 → 파일 쓰기 |
| `Files` | 파일 목록 조회 |
| `FileInfo` | 파일 메타데이터 조회 |
| `DirectoryCreate` | 디렉토리 생성 |
| `Zip` | 압축/해제 |
### Git
| 커맨드 | 설명 |
|--------|------|
| `Git` | 임의 git 명령 실행 |
| `GitCommit` | 변경사항 커밋 |
| `GitPull` | pull |
| `GitPush` | push |
| `GitCheckout` | 브랜치/파일 체크아웃 |
| `GitRev` | 리비전 해시 조회 |
| `GitCount` | 커밋 수 조회 |
| `GitReset` | reset |
| `GitStashPush` / `GitStashApply` | stash 관리 |
| `GitBranch` | 브랜치 목록/생성/삭제 |
| `GitHub` | GitHub API 호출 |
| `GitHubPullRequestCreate` / `List` / `Close` | PR 관리 |
### 알림
| 커맨드 | 설명 |
|--------|------|
| `Slack` | Slack 메시지 전송 |
| `SlackFile` | Slack 파일 업로드 |
| `SlackBuild` | 빌드 결과 Slack 리포트 |
| `Mattermost` | Mattermost 메시지 전송 |
| `MattermostBuild` | 빌드 결과 Mattermost 리포트 |
### 네트워크 / FTP
| 커맨드 | 설명 |
|--------|------|
| `Upload` | FTP 업로드 |
| `Download` | FTP 다운로드 |
| `WebRequest` | HTTP 요청 |
| `WebFile` | HTTP 파일 다운로드 |
| `URLInfo` | URL 정보 파싱 |
### 문자열 / 유틸
| 커맨드 | 설명 |
|--------|------|
| `Print` | 메시지 출력 |
| `SetValue` | property 값 직접 설정 |
| `Delay` | 지연 (초) |
| `Timer` | 경과 시간 측정 |
| `StringSub` | 문자열 자르기 |
| `StringReplace` | 문자열 치환 |
| `StringReplacePattern` | 정규식 치환 |
| `StringIndex` | 인덱스 검색 |
| `JsonReader` | JSON 값 읽기 |
| `JsonReaderFile` | JSON 파일 읽기 |
| `JsonWriterFile` | JSON 파일 쓰기 |
### 셸 / 프로세스
| 커맨드 | 설명 |
|--------|------|
| `Shell` | 셸 명령 실행 |
| `ShellFile` | 셸 스크립트 파일 실행 |
| `ProcessRun` | 프로세스 실행 |
| `ProcessKill` | 프로세스 종료 |
| `ProcessPortUse` | 포트 사용 여부 확인 |
### 외부 서비스
| 커맨드 | 설명 |
|--------|------|
| `Jira` | Jira 이슈 조회/수정 |
| `AwsCli` | AWS CLI 명령 실행 |
| `Docker` | Docker 명령 실행 |
| `Gradle` | Gradle 빌드 실행 |
| `Protobuf` | protoc 컴파일 |
| `JenkinsParameterModify` | Jenkins 파라미터 수정 |
| `SMBAuth` | SMB 인증 |
---
## 데이터 모델 구조
모든 커맨드 파라미터는 `DataParam`을 상속한다.
```dart
DataParam
├── workspace?: String // 기본 작업 디렉토리
├── passExitCodes?: List<int> // 성공으로 간주할 exit code
├── setResult?: String // 결과 저장 태그
├── setExitCode?: String // exit code 저장 태그
└── showPrint?: bool
// 예시
DataBuildiOS extends DataParam {
workspacePath: String
scheme: String
destination: String
...
}
```
**워크스페이스 해석 우선순위:**
1. `param.workspace`
2. `property.workspace`
3. `DataCommon.workspace` (Jenkins env)
---
## 새 커맨드 추가
```
1. lib/oto/data/*.dart DataParam 상속 모델 정의 + @JsonSerializable
2. lib/oto/commands/command.dart CommandType enum에 값 추가
3. lib/oto/commands/{category}/ Command 상속 클래스 구현
4. lib/oto/commands/command_registry.dart registerAllCommands()에 등록
5. dart run build_runner build *.g.dart 생성
```
---
## 스케줄러
```bash
oto scheduler -r build.yaml # 스케줄러 등록
oto scheduler -l # 목록 조회
oto scheduler -e <alias> true # 활성화/비활성화
oto scheduler -u <alias> # 해제
```
**YAML 스케줄러 설정:**
```yaml
scheduler:
alias: nightly-build
cron: "0 2 * * *" # cron 표현식 또는
# interval: 3600 # 초 단위 interval
enableLog: true
```
**플랫폼별 실행 메커니즘:**
- **Linux:** cron
- **macOS:** LaunchAgent
- **Windows:** Task Scheduler
---
## 프로젝트 구조
```
lib/
├── cli/ # CLI 인자 파싱, 스케줄러 관리
├── framework/ # 플랫폼 추상화, ProcessExecutor, 로깅
└── oto/
├── application.dart # 싱글턴 오케스트레이터
├── commands/ # 커맨드 구현체 (카테고리별)
├── core/ # 태그 시스템, 데이터 합성, 정의 데이터
├── data/ # JSON 직렬화 데이터 모델 (*.g.dart 포함)
├── pipeline/ # 파이프라인 흐름 제어 엔진
└── utils/ # 공통 유틸리티
```
---
## 기술 스택
- **언어:** Dart (SDK >=2.18.6 <4.0.0)
- **의존성:** dart_framework (커스텀), http, json_annotation, cron, xml
- **코드 생성:** json_serializable + build_runner
```bash
dart run build_runner build # 데이터 모델 생성
```
---
## 에러 처리
- `Application.build()``catch (e, stacktrace)`로 Exception과 Error 모두 처리
- 실패 시 `exit(10)` 으로 부모 프로세스에 실패 신호 전달
- `passExitCodes`로 성공으로 간주할 exit code 지정 가능

View file

@ -1 +1 @@
1.1.3
1.1.7

View file

@ -98,14 +98,21 @@ apply_entry_files() {
# ── 사용법 ────────────────────────────────────────────────────────────────────
usage() {
echo "사용법: $0 <target>"
echo "사용법: $0 [--pull] <target>"
echo ""
echo " 현재 프로젝트 → target 으로 동기화"
echo " (기본) 현재 프로젝트 → target(agentic-framework) 으로 push"
echo " --pull target(agentic-framework) → 현재 프로젝트 로 pull"
echo ""
echo " target: 폴더명 (동일 레벨 탐색) | 상대경로 | 절대경로"
}
# ─────────────────────────────────────────────────────────────────────────────
PULL_MODE="0"
if [[ "${1:-}" == "--pull" ]]; then
PULL_MODE="1"
shift
fi
TARGET_INPUT="${1:-}"
if [[ -z "$TARGET_INPUT" ]]; then
usage; exit 1
@ -121,6 +128,32 @@ SRC_AO="$AGENT_OPS_DIR"
DST_AO="$TARGET/agent-ops"
IS_FRAMEWORK="$([[ -f "$PROJECT_ROOT/.agent-ops-source" ]] && echo "1" || echo "0")"
# ── pull 모드: agentic-framework → 현재 프로젝트 ─────────────────────────────
if [[ "$PULL_MODE" == "1" ]]; then
if [[ "$IS_FRAMEWORK" == "1" ]]; then
echo -e "${RED}Error: agentic-framework에서는 --pull을 사용할 수 없습니다.${RESET}"
exit 1
fi
if [[ ! -f "$TARGET/.agent-ops-source" ]]; then
echo -e "${RED}Error: target이 agentic-framework가 아닙니다 (.agent-ops-source 없음)${RESET}"
exit 1
fi
SRC_VER="$(cat "$SRC_AO/.version" 2>/dev/null || echo "0.0.0")"
DST_VER="$(cat "$DST_AO/.version" 2>/dev/null || echo "0.0.0")"
echo "$(basename "$TARGET")$(basename "$PROJECT_ROOT") (pull)"
echo " 현재 버전: $SRC_VER | framework 버전: $DST_VER"
sync_common "$DST_AO" "$SRC_AO"
cp "$DST_AO/.version" "$SRC_AO/.version"
apply_entry_files "$DST_AO/rules/common/rules.md" "$PROJECT_ROOT"
cd "$PROJECT_ROOT"
git add agent-ops/rules/common agent-ops/skills/common agent-ops/bin agent-ops/.version \
CLAUDE.md GEMINI.md .cursorrules AGENTS.md
git commit -m "sync: pull from agentic-framework v$DST_VER"
git push
echo -e "${GREEN}✓ 완료 (pull v$DST_VER)${RESET}"
exit 0
fi
echo "$(basename "$PROJECT_ROOT")$(basename "$TARGET")"
# ── agentic-framework에서 다른 프로젝트로 ────────────────────────────────────
@ -132,13 +165,14 @@ if [[ "$IS_FRAMEWORK" == "1" ]]; then
else
echo " 이후 진행: common/ 동기화"
sync_common "$SRC_AO" "$DST_AO"
cp "$SRC_AO/.version" "$DST_AO/.version"
apply_entry_files "$SRC_AO/rules/common/rules.md" "$TARGET"
fi
echo -e "${GREEN}✓ 완료 → $(basename "$TARGET")${RESET}"
exit 0
fi
# ── 일반 프로젝트에서 agentic-framework로 ────────────────────────────────────
# ── 일반 프로젝트에서 agentic-framework로 (push) ─────────────────────────────
if [[ ! -f "$TARGET/.agent-ops-source" ]]; then
echo -e "${RED}Error: target이 agentic-framework가 아닙니다 (.agent-ops-source 없음)${RESET}"
exit 1
@ -149,9 +183,8 @@ DST_VER="$(cat "$DST_AO/.version" 2>/dev/null || echo "0.0.0")"
echo " 현재 버전: $SRC_VER | framework 버전: $DST_VER"
if version_gt "$DST_VER" "$SRC_VER"; then
sync_common "$SRC_AO" "$DST_AO"
echo -e "${RED}⚠ 버전 충돌: framework($DST_VER) > current($SRC_VER)${RESET}"
echo -e "${YELLOW} 파일은 복사됐지만 push하지 않았습니다. 수동 머지 후 재시도하세요.${RESET}"
echo -e "${YELLOW} 먼저 sync-pull로 내려받은 뒤 재시도하세요.${RESET}"
exit 1
fi

View file

@ -0,0 +1,73 @@
# sample
## 목적 / 책임
YAML 작성 요청 시 코드 분석 없이 빠르게 참조할 수 있는 샘플 파일 모음을 담당한다.
샘플만 읽으면 파라미터 이름·태그 문법·파이프라인 패턴을 바로 파악할 수 있어 토큰을 절약한다.
## 포함 경로
- `assets/yaml/sample/` — 카테고리별 샘플 YAML 파일
## 제외 경로
- `assets/yaml/example/` — 기존 실험용 파일 (샘플 도메인 아님)
- `assets/templates/` — 알림 템플릿 (샘플 도메인 아님)
## 주요 구성 요소
| 파일 | 다루는 커맨드 / 패턴 |
|------|-------------------|
| `01_basic.yaml` | property, SetValue, Print — 기본 구조 |
| `02_pipeline_if.yaml` | if (string/int/version 타입 조건 분기) |
| `03_pipeline_foreach.yaml` | foreach (List·Map 순회, 중첩) |
| `04_pipeline_exe_handle.yaml` | exe-handle (on-success / on-fail) |
| `05_pipeline_while_switch.yaml` | while, switch, wait-until |
| `06_git.yaml` | Git, GitCommit, GitPush, GitRev, GitCount, GitBranch |
| `07_file.yaml` | Copy, Delete, FileRead, FileWrite, Zip, FileInfo |
| `08_notification.yaml` | Slack, SlackBuild, Mattermost, MattermostBuild |
| `09_network.yaml` | WebRequest, WebFile, Upload(FTP), Download(FTP) |
| `10_build_ios.yaml` | BuildiOS, ArchiveiOS, ExportiOS, TestflightUpload, TestflightDistribute |
| `11_build_flutter.yaml` | BuildFlutter (멀티플랫폼) |
| `12_scheduler.yaml` | scheduler 섹션 (cron / interval) |
| `13_string_json.yaml` | StringSub, StringReplace, JsonReader, JsonReaderFile, JsonWriterFile |
| `14_shell_process.yaml` | Shell, ShellFile, ProcessRun, ProcessKill, ProcessPortUse |
## YAML 작성 요청 시 참조 가이드
요청 내용에 따라 아래 샘플만 읽으면 충분하다. 코드를 읽지 않는다.
| 요청 키워드 | 읽을 샘플 |
|------------|---------|
| iOS 빌드, Archive, TestFlight | `10_build_ios.yaml` |
| Flutter 빌드 | `11_build_flutter.yaml` |
| Git, 커밋, 푸시, 브랜치 | `06_git.yaml` |
| 파일 복사, 압축, 삭제 | `07_file.yaml` |
| Slack, Mattermost 알림 | `08_notification.yaml` |
| HTTP, FTP, 네트워크 | `09_network.yaml` |
| if 분기 | `02_pipeline_if.yaml` |
| foreach, 반복 | `03_pipeline_foreach.yaml` |
| 성공/실패 분기 | `04_pipeline_exe_handle.yaml` |
| while, switch | `05_pipeline_while_switch.yaml` |
| 스케줄러 | `12_scheduler.yaml` |
| 문자열, JSON | `13_string_json.yaml` |
| 셸, 프로세스 | `14_shell_process.yaml` |
| 처음 만드는 파이프라인, 기본 구조 | `01_basic.yaml` |
## 유지할 패턴
- 파일명은 `NN_카테고리.yaml` 형식 (번호 + 언더스코어 + 카테고리)
- 각 파일 상단에 `# [샘플] 제목` 주석으로 목적 명시
- 실제 작동 가능한 파라미터 구조를 유지한다 (추측 값 사용 금지)
- 새 커맨드가 추가되면 관련 샘플 파일도 함께 갱신한다
## 다른 도메인과의 경계
- **command**: 커맨드 파라미터가 변경되면 샘플도 동기화 필요. 샘플은 command 도메인을 참조하지 않는다
- **core**: 태그 문법(`<!...>` / `<@...>`)이 변경되면 모든 샘플 파일 업데이트 필요
## 금지 사항
- 실제 코드(Dart 파일)를 읽어 파라미터를 추론하지 않는다 — 샘플에서 직접 읽는다
- 샘플에 실제 토큰·비밀번호·API 키를 기재하지 않는다 (placeholder 사용)
- 존재하지 않는 커맨드나 파라미터를 샘플에 넣지 않는다

View file

@ -86,3 +86,10 @@ lib/
| `lib/oto/core/**` | core | `agent-ops/rules/project/domain/core/rules.md` |
| `lib/oto/utils/**` | core | `agent-ops/rules/project/domain/core/rules.md` |
| `lib/framework/**` | framework | `agent-ops/rules/project/domain/framework/rules.md` |
| `assets/yaml/sample/**` | sample | `agent-ops/rules/project/domain/sample/rules.md` |
## 스킬 라우팅
| 요청 키워드 | 수행 방법 |
|------------|---------|
| yaml 짜줘, 파이프라인 만들어줘, 자동화 작성, 빌드 yaml | `agent-ops/rules/project/domain/sample/rules.md` 읽고 해당 샘플 참조 |

View file

@ -8,5 +8,6 @@
| 계획 세워줘, 구현 계획, PLAN.md, plan | `agent-ops/skills/common/plan/SKILL.md` |
| 코드 리뷰해줘, code review, CODE_REVIEW.md, 리뷰 루프 | `agent-ops/skills/common/code-review/SKILL.md` |
| 커밋해줘, 푸시해줘, commit, push, 반영해줘 | `agent-ops/skills/common/commit-push/SKILL.md` |
| agent-ops 싱크해, agent-ops 동기화해, agentic-framework에 올려줘, agent-ops를 [프로젝트]로 싱크해 | `agent-ops/skills/common/sync-agent-ops/SKILL.md` |
| agent-ops 싱크해, agent-ops 동기화해, agentic-framework에 올려줘, agent-ops를 [프로젝트]로 싱크해 | `agent-ops/skills/common/sync-push/SKILL.md` |
| agent-ops pull해, agent-ops 가져와, agentic-framework에서 가져와, agent-ops 내려받아 | `agent-ops/skills/common/sync-pull/SKILL.md` |
| 도메인 업데이트, domain rule 갱신, 도메인 검토, domain 스캔 | `agent-ops/skills/common/update-domain-rule/SKILL.md` |

View file

@ -0,0 +1,36 @@
---
name: sync-pull
description: agentic-framework에서 현재 프로젝트로 agent-ops를 내려받는다. "agent-ops pull해", "agentic-framework에서 가져와" 요청 시 사용한다.
---
# sync-pull
## 목적
`agent-ops/bin/sync.sh --pull`을 호출해 agentic-framework → 현재 프로젝트 방향으로 agent-ops를 내려받는다.
## 언제 호출할지
- "agent-ops pull해", "agent-ops 가져와" 요청 시
- "agentic-framework에서 가져와" 요청 시
- "agent-ops 내려받아" 요청 시
## 실행 절차
1. 현재 프로젝트의 **상위 폴더(`../`)**`agentic-framework` 폴더가 있는지 확인한다 (현재 폴더 내부가 아님)
2. **있으면**: `agent-ops/bin/sync.sh --pull agentic-framework` 실행
3. **없으면**: 사용자에게 agentic-framework 경로 입력을 안내하고, 입력받은 경로로 실행
```bash
agent-ops/bin/sync.sh --pull <target>
```
## 실행 결과 검증
- [ ] sync.sh 가 오류 없이 완료됐는가
- [ ] 현재 프로젝트 버전이 framework 버전과 일치하는가
- [ ] 진입 파일(CLAUDE.md 등)이 갱신됐는가
## 금지 사항
- sync.sh 를 거치지 않고 직접 파일을 복사하지 않는다

View file

@ -1,14 +1,13 @@
---
name: sync-agent-ops
description: agent-ops를 agentic-framework와 동기화한다. "agent-ops 싱크해", "agent-ops 동기화해" 요청 시 사용한다.
name: sync-push
description: 현재 프로젝트의 agent-ops를 agentic-framework로 올린다. "agent-ops 싱크해", "agent-ops 동기화해", "agentic-framework에 올려줘" 요청 시 사용한다.
---
# sync-agent-ops
# sync-push
## 목적
`agent-ops/bin/sync.sh`를 호출해 agent-ops를 동기화한다.
현재 프로젝트가 agentic-framework인지 여부에 따라 동작이 달라진다.
`agent-ops/bin/sync.sh`를 호출해 현재 프로젝트 → agentic-framework 방향으로 agent-ops를 올린다.
## 언제 호출할지
@ -20,7 +19,7 @@ description: agent-ops를 agentic-framework와 동기화한다. "agent-ops 싱
### 현 프로젝트가 일반 프로젝트인 경우 (`.agent-ops-source` 없음)
1. 동일 레벨`agentic-framework` 폴더가 있는지 확인한다
1. 현재 프로젝트의 **상위 폴더(`../`)** `agentic-framework` 폴더가 있는지 확인한다 (현재 폴더 내부가 아님)
2. **있으면**: `agent-ops/bin/sync.sh agentic-framework` 실행
3. **없으면**: 사용자에게 agentic-framework 경로 입력을 안내하고, 입력받은 경로로 실행

View file

@ -0,0 +1,37 @@
---
# [샘플] 기본 구조
# property, commands, pipeline 세 섹션으로 구성된다.
# pipeline의 exe는 commands의 id를 참조한다.
property:
workspace: /path/to/project
env: dev
version: 1.0.0
pipeline:
id: main
workflow:
- exe: print-start
- exe: set-value
- exe: print-result
commands:
- command: Print
id: print-start
param:
message: "빌드 시작 - env: <!property.env>, version: <!property.version>"
- command: SetValue
id: set-value
param:
values-string:
buildTag: "v<!property.version>-<!property.env>"
values-int:
retryCount: 0
values-bool:
isRelease: false
- command: Print
id: print-result
param:
message: "빌드 태그: <!property.buildTag>"

View file

@ -0,0 +1,53 @@
---
# [샘플] 조건 분기 (if)
# condition-string / condition-int / condition-float / condition-bool /
# condition-date / condition-version 으로 타입 지정
# on-true / on-false 로 분기
property:
workspace: /path/to/project
env: prod
pipeline:
id: main
workflow:
# string 비교
- if:
condition-string: <!property.env> == prod
on-true:
- exe: deploy-prod
on-false:
- exe: deploy-dev
# int 비교
- if:
condition-int: <!property.retryCount> < 3
on-true:
- exe: retry-build
# version 비교 (자릿수 동일해야 함: 0.0.0 or 0.0.0.0)
- if:
condition-version: <!property.version> >= 2.0.0
on-true:
- exe: new-flow
commands:
- command: Print
id: deploy-prod
param:
message: "운영 배포 실행"
- command: Print
id: deploy-dev
param:
message: "개발 배포 실행"
- command: Print
id: retry-build
param:
message: "재시도 실행"
- command: Print
id: new-flow
param:
message: "신규 플로우 실행"

View file

@ -0,0 +1,60 @@
---
# [샘플] 반복 (foreach)
# iterator: List 또는 Map
# setValue: 현재 값 저장 태그
# setKey: Map 순회 시 key 저장 태그 (optional)
property:
workspace: /path/to/project
platforms:
- ios
- android
envMap:
dev: https://dev.example.com
prod: https://prod.example.com
pipeline:
id: main
workflow:
# List 순회
- foreach:
iterator: <!property.platforms>
setValue: <@property.platform>
on-do:
- exe: print-platform
# Map 순회 (key + value)
- foreach:
iterator: <!property.envMap>
setKey: <@property.envName>
setValue: <@property.envUrl>
on-do:
- exe: print-env
# 중첩 foreach
- foreach:
iterator: <!property.platforms>
setValue: <@property.platform>
on-do:
- foreach:
iterator: <!property.envMap>
setKey: <@property.envName>
setValue: <@property.envUrl>
on-do:
- exe: print-matrix
commands:
- command: Print
id: print-platform
param:
message: "플랫폼: <!property.platform>"
- command: Print
id: print-env
param:
message: "환경: <!property.envName> → <!property.envUrl>"
- command: Print
id: print-matrix
param:
message: "<!property.platform> / <!property.envName>: <!property.envUrl>"

View file

@ -0,0 +1,46 @@
---
# [샘플] 성공/실패 분기 (exe-handle)
# on-success: 커맨드가 성공(exit 0)했을 때
# on-fail: 커맨드가 실패(exit 非0)했을 때
# passExitCodes: 성공으로 간주할 exit code 추가 지정
property:
workspace: /path/to/project
pipeline:
id: main
workflow:
- exe-handle:
id: build-ios
on-success:
- exe: notify-success
- exe: upload-artifact
on-fail:
- exe: notify-fail
commands:
- command: BuildiOS
id: build-ios
param:
workspacePath: <!property.workspace>/ios/Runner.xcworkspace
scheme: Runner
destination: "generic/platform=iOS"
- command: Slack
id: notify-success
param:
token: xoxb-your-slack-token
channel: "#builds"
message: "✅ iOS 빌드 성공"
- command: Slack
id: notify-fail
param:
token: xoxb-your-slack-token
channel: "#builds"
message: "❌ iOS 빌드 실패"
- command: Print
id: upload-artifact
param:
message: "아티팩트 업로드 중..."

View file

@ -0,0 +1,66 @@
---
# [샘플] 루프(while) + 멀티케이스(switch)
property:
workspace: /path/to/project
retryCount: 0
maxRetry: 3
status: pending
pipeline:
id: main
workflow:
# while: 조건이 참인 동안 반복
- while:
condition-int: <!property.retryCount> < <!property.maxRetry>
on-do:
- exe: check-status
- exe: increment-retry
# switch: 값에 따라 분기
- switch:
condition-string: <!property.status>
cases:
- value: success
tasks:
- exe: notify-success
- value: failed
tasks:
- exe: notify-fail
- value: pending
tasks:
- exe: notify-pending
# wait-until: 비동기 작업 완료 대기
# - async: long-running-task
# - wait-until-string: <!state.long-running-task> == complete
# wait-until-seconds: N초 단순 대기
# - wait-until-seconds: 30
commands:
- command: Print
id: check-status
param:
message: "상태 확인 중... (시도: <!property.retryCount>)"
- command: SetValue
id: increment-retry
param:
values-int:
retryCount: 1
- command: Print
id: notify-success
param:
message: "성공"
- command: Print
id: notify-fail
param:
message: "실패"
- command: Print
id: notify-pending
param:
message: "대기중"

View file

@ -0,0 +1,76 @@
---
# [샘플] Git 작업
property:
workspace: /path/to/project
branch: main
commitMessage: "chore: automated update"
pipeline:
id: main
workflow:
- exe: git-update # 최신 소스로 업데이트
- exe: git-rev # 현재 리비전 저장
- exe: git-count # 커밋 수 저장
- exe: print-info
# 변경사항이 있을 때만 커밋 + 푸시
- if:
condition-bool: <!property.hasChange> == true
on-true:
- exe: git-commit
- exe: git-push
commands:
# 임의 git 명령 조합
- command: Git
id: git-update
param:
commands:
- fetch origin
- reset --hard origin/<!property.branch>
- clean -fd
# 브랜치 체크아웃
- command: GitCheckout
id: git-checkout
param:
branch: <!property.branch>
execute-pull: true
# 현재 리비전 해시 저장
- command: GitRev
id: git-rev
param:
result-value: <@property.gitRev>
# 전체 커밋 수 저장
- command: GitCount
id: git-count
param:
result-value: <@property.gitCount>
- command: Print
id: print-info
param:
message: "rev: <!property.gitRev>, count: <!property.gitCount>"
# 변경 파일 스테이징 + 커밋
- command: GitCommit
id: git-commit
param:
message: <!property.commitMessage>
addNewFile: true
# 원격 브랜치에 푸시
- command: GitPush
id: git-push
param:
branch: <!property.branch>
# 브랜치 목록 조회 (최근 N일)
- command: GitBranch
id: git-branch-list
param:
path: <!property.workspace>
startDay: 30
setBranches: <@property.branches>

View file

@ -0,0 +1,85 @@
---
# [샘플] 파일 조작
property:
workspace: /path/to/project
version: 1.0.0
pipeline:
id: main
workflow:
- exe: read-config # 파일 읽기
- exe: string-replace # 내용 치환
- exe: write-config # 파일 쓰기
- exe: copy-release # 복사
- exe: zip-release # 압축
- exe: delete-temp # 임시 파일 삭제
commands:
# 파일 전체 내용 읽기 → property 저장
- command: FileRead
id: read-config
param:
path: <!property.workspace>/config/app.conf
setContent: <@property.configContent>
showPrint: false
# 문자열 치환
- command: StringReplace
id: string-replace
param:
text: <!property.configContent>
pair:
- from: "VERSION_PLACEHOLDER"
to: <!property.version>
- from: "ENV_PLACEHOLDER"
to: prod
setValue: <@property.configContent>
# 파일에 내용 쓰기
- command: FileWrite
id: write-config
param:
path: <!property.workspace>/build/app.conf
content: <!property.configContent>
# 파일/디렉토리 복사 (copyMap: from → to)
- command: Copy
id: copy-release
param:
ignorePattern:
- "*.log"
- ".DS_Store"
copyMap:
"<!property.workspace>/build/*": "<!property.workspace>/release"
# 이동 (isMove: true)
# - command: Copy
# id: move-file
# param:
# isMove: true
# copyMap:
# "<!property.workspace>/tmp/output": "<!property.workspace>/release"
# 압축
- command: Zip
id: zip-release
param:
zipFile: "<!property.workspace>/dist/release_v<!property.version>.zip"
zipList:
- "<!property.workspace>/release"
# 파일/디렉토리 삭제
- command: Delete
id: delete-temp
param:
list:
- "<!property.workspace>/tmp"
- "<!property.workspace>/build"
# 파일 메타데이터 조회
- command: FileInfo
id: file-info
param:
path: "<!property.workspace>/dist/release_v<!property.version>.zip"
setInfo: <@property.fileInfo>

View file

@ -0,0 +1,67 @@
---
# [샘플] 알림 (Slack / Mattermost)
property:
workspace: /path/to/project
appName: MyApp
version: 1.0.0
downloadURL: https://example.com/download/MyApp.ipa
pipeline:
id: main
workflow:
- exe: slack-message
- exe: slack-build-report
- exe: mattermost-message
- exe: mattermost-build-report
commands:
# Slack 텍스트 메시지
- command: Slack
id: slack-message
param:
token: xoxb-your-slack-bot-token
channel: "#ci-builds"
message: "배포 시작: <!property.appName> v<!property.version>"
# Slack 파일 업로드
# - command: SlackFile
# id: slack-file
# param:
# token: xoxb-your-slack-bot-token
# channel: "#ci-builds"
# filePath: <!property.workspace>/output/result.txt
# message: "빌드 결과 로그"
# Slack 빌드 결과 리포트 (구조화된 메시지)
- command: SlackBuild
id: slack-build-report
param:
token: xoxb-your-slack-bot-token
channel: "#ci-builds"
appName: <!property.appName>
type: ios
version: <!property.version>
downloadURL: <!property.downloadURL>
# Mattermost 텍스트 메시지
- command: Mattermost
id: mattermost-message
param:
url: https://mattermost.example.com/api/v4/posts
token: your-mattermost-token
channelId: your-channel-id
message: "배포 완료: <!property.appName> v<!property.version>"
# Mattermost 빌드 결과 리포트
- command: MattermostBuild
id: mattermost-build-report
param:
url: https://mattermost.example.com/api/v4/posts
token: your-mattermost-token
channelId: your-channel-id
appName: <!property.appName>
type: ios
color: 45C45E
version: <!property.version>
downloadURL: <!property.downloadURL>

View file

@ -0,0 +1,80 @@
---
# [샘플] 네트워크 / HTTP / FTP
property:
workspace: /path/to/project
pipeline:
id: main
workflow:
- exe: http-get # HTTP GET → 응답 저장
- exe: json-parse # 응답 JSON 파싱
- exe: http-post # HTTP POST
- exe: ftp-upload # FTP 업로드
- exe: ftp-download # FTP 다운로드
- exe: download-file # HTTP 파일 다운로드
commands:
# HTTP GET, 응답 body 저장
- command: WebRequest
id: http-get
param:
url: https://api.example.com/status
method: GET
headers:
Authorization: Bearer your-token
setResponse: <@property.apiResponse>
# 응답 JSON에서 값 추출
- command: JsonReader
id: json-parse
param:
json: <!property.apiResponse>
pair:
- from: status
setValue: <@property.apiStatus>
- from: version
setValue: <@property.serverVersion>
# HTTP POST
- command: WebRequest
id: http-post
param:
url: https://api.example.com/deploy
method: POST
headers:
Content-Type: application/json
Authorization: Bearer your-token
body:
appId: com.example.app
version: <!property.version>
setResponse: <@property.deployResponse>
# HTTP 파일 다운로드
- command: WebFile
id: download-file
param:
url: https://example.com/tools/tool.zip
savePath: <!property.workspace>/tools/tool.zip
# FTP 업로드 (copyMap: local → remote)
- command: Upload
id: ftp-upload
param:
host: ftp.example.com
port: 21
id: ftp-user
password: ftp-password
copyMap:
"<!property.workspace>/release/app.ipa": "/public/releases/app.ipa"
# FTP 다운로드 (copyMap: remote → local)
- command: Download
id: ftp-download
param:
host: ftp.example.com
port: 21
id: ftp-user
password: ftp-password
copyMap:
"/public/config/app.json": "<!property.workspace>/config/app.json"

View file

@ -0,0 +1,104 @@
---
# [샘플] iOS 전체 빌드 파이프라인
# Build → Archive → Export → TestFlight 업로드 → 배포
property:
workspace: /path/to/project
scheme: Runner
configuration: Release
version: 1.0.0
exportOptionsPath: <!property.workspace>/ios/ExportOptions.plist
archivePath: <!property.workspace>/build/Runner.xcarchive
exportPath: <!property.workspace>/build/export
ipaPath: <!property.exportPath>/Runner.ipa
pipeline:
id: main
workflow:
- exe: build-ios
- exe-handle:
id: archive-ios
on-success:
- exe: export-ios
- exe: upload-testflight
- exe: check-testflight
- exe: distribute-testflight
- exe: notify-success
on-fail:
- exe: notify-fail
commands:
# xcodebuild build (시뮬레이터 테스트용)
- command: BuildiOS
id: build-ios
param:
# xcworkspace 우선, 없으면 xcodeproj 사용
workspacePath: <!property.workspace>/ios/Runner.xcworkspace
scheme: <!property.scheme>
configuration: <!property.configuration>
destination: "generic/platform=iOS Simulator"
# xcodebuild archive
- command: ArchiveiOS
id: archive-ios
param:
workspacePath: <!property.workspace>/ios/Runner.xcworkspace
scheme: <!property.scheme>
configuration: <!property.configuration>
archivePath: <!property.archivePath>
# .xcarchive → .ipa export
- command: ExportiOS
id: export-ios
param:
archivePath: <!property.archivePath>
exportPath: <!property.exportPath>
exportOptionsPlist: <!property.exportOptionsPath>
# TestFlight 업로드 (altool / notarytool)
- command: TestflightUpload
id: upload-testflight
param:
ipaPath: <!property.ipaPath>
username: apple-id@example.com
password: "@keychain:app-specific-password"
# 처리 완료 대기
- command: TestflightStatusCheck
id: check-testflight
param:
username: apple-id@example.com
password: "@keychain:app-specific-password"
bundleId: com.example.app
version: <!property.version>
# 테스터 그룹에 배포
- command: TestflightDistribute
id: distribute-testflight
param:
username: apple-id@example.com
password: "@keychain:app-specific-password"
bundleId: com.example.app
version: <!property.version>
groups:
- Internal Testers
- command: MattermostBuild
id: notify-success
param:
url: https://mattermost.example.com/api/v4/posts
token: your-mattermost-token
channelId: your-channel-id
appName: MyApp
type: ios
color: 45C45E
version: <!property.version>
downloadURL: https://testflight.apple.com/join/xxxxxx
- command: Mattermost
id: notify-fail
param:
url: https://mattermost.example.com/api/v4/posts
token: your-mattermost-token
channelId: your-channel-id
message: "❌ iOS 빌드 실패 - v<!property.version>"

View file

@ -0,0 +1,55 @@
---
# [샘플] Flutter 멀티플랫폼 빌드
property:
workspace: /path/to/flutter_project
version: 1.0.0
flavor: production
platforms:
- ios
- android
pipeline:
id: main
workflow:
- exe: git-update
- foreach:
iterator: <!property.platforms>
setValue: <@property.platform>
on-do:
- if:
condition-string: <!property.platform> == ios
on-true:
- exe: build-ios
on-false:
- exe: build-android
- exe: notify-done
commands:
- command: Git
id: git-update
param:
commands:
- fetch origin
- reset --hard origin/main
- command: BuildFlutter
id: build-ios
param:
targetPath: <!property.workspace>
platform: ios
flavor: <!property.flavor>
additional: "--release --no-codesign"
- command: BuildFlutter
id: build-android
param:
targetPath: <!property.workspace>
platform: android
flavor: <!property.flavor>
additional: "--release"
- command: Print
id: notify-done
param:
message: "<!property.platform> 빌드 완료"

View file

@ -0,0 +1,40 @@
---
# [샘플] 스케줄러 설정
# oto scheduler -r 12_scheduler.yaml 으로 등록
# oto scheduler -l 으로 목록 확인
# oto scheduler -u nightly-build 으로 해제
scheduler:
alias: nightly-build
cron: "0 2 * * *" # 매일 새벽 2시 (cron 형식)
# interval: 3600000 # 또는 ms 단위 interval (1시간)
enableLog: true # 로그 파일 기록 여부
property:
workspace: /path/to/project
branch: main
version: 1.0.0
pipeline:
id: main
workflow:
- exe: git-update
- exe: build
- exe: notify
commands:
- command: Git
id: git-update
param:
commands:
- fetch origin
- reset --hard origin/<!property.branch>
- command: BuildDart
id: build
param: {}
- command: Print
id: notify
param:
message: "나이틀리 빌드 완료"

View file

@ -0,0 +1,96 @@
---
# [샘플] 문자열 처리 + JSON 읽기/쓰기
property:
workspace: /path/to/project
rawVersion: "v1.2.3-release"
payload: '{"status": "ok", "build": {"version": "1.2.3", "number": 42}}'
pipeline:
id: main
workflow:
- exe: string-sub # 부분 문자열 추출
- exe: string-replace # 문자열 치환
- exe: string-pattern # 정규식 치환
- exe: string-index # 인덱스 검색
- exe: json-read # JSON 값 추출
- exe: json-read-file # JSON 파일 읽기
- exe: json-write-file # JSON 파일 쓰기
- exe: print-results
commands:
# 부분 문자열 (startIndex, lastIndex: 0-based, -1은 끝까지)
- command: StringSub
id: string-sub
param:
text: <!property.rawVersion>
startIndex: 1 # "v" 제거
lastIndex: -1
setValue: <@property.version>
# 문자열 치환
- command: StringReplace
id: string-replace
param:
text: <!property.rawVersion>
pair:
- from: "v"
to: ""
- from: "-release"
to: ""
setValue: <@property.cleanVersion>
# 정규식 치환
- command: StringReplacePattern
id: string-pattern
param:
text: <!property.rawVersion>
pattern: "[^0-9.]" # 숫자와 점만 남김
replace: ""
setValue: <@property.numericVersion>
# 문자열 내 인덱스 검색
- command: StringIndex
id: string-index
param:
text: <!property.rawVersion>
find: "-"
setValue: <@property.dashIndex>
# JSON 문자열에서 값 추출
- command: JsonReader
id: json-read
param:
json: <!property.payload>
pair:
- from: status
setValue: <@property.apiStatus>
- from: build.version # 중첩 키
setValue: <@property.buildVersion>
- from: build.number
setValue: <@property.buildNumber>
# JSON 파일에서 값 읽기
- command: JsonReaderFile
id: json-read-file
param:
path: <!property.workspace>/config/settings.json
pair:
- from: apiKey
setValue: <@property.apiKey>
# JSON 파일에 값 쓰기
- command: JsonWriterFile
id: json-write-file
param:
path: <!property.workspace>/config/settings.json
pair:
- from: version
value: <!property.cleanVersion>
- from: buildNumber
value: <!property.buildNumber>
- command: Print
id: print-results
param:
message: "버전: <!property.cleanVersion>, 빌드: <!property.buildNumber>, 상태: <!property.apiStatus>"

View file

@ -0,0 +1,61 @@
---
# [샘플] 셸 실행 + 프로세스 제어
property:
workspace: /path/to/project
serverPort: 8080
pipeline:
id: main
workflow:
- exe: check-port # 포트 사용 중인지 확인
- if:
condition-bool: <!property.portInUse> == true
on-true:
- exe: kill-process # 기존 프로세스 종료
- exe: run-server # 서버 프로세스 실행
- exe: shell-cmd # 셸 명령 실행
- exe: shell-script # 셸 스크립트 파일 실행
commands:
# 포트 사용 여부 확인
- command: ProcessPortUse
id: check-port
param:
port: <!property.serverPort>
setPortUse: <@property.portInUse>
# 특정 포트 프로세스 종료
- command: ProcessKill
id: kill-process
param:
port: <!property.serverPort>
# 프로세스 백그라운드 실행
- command: ProcessRun
id: run-server
param:
command: ./server
args:
- --port
- <!property.serverPort>
setProcessId: <@property.serverPid>
# 인라인 셸 명령 (여러 줄)
- command: Shell
id: shell-cmd
param:
commands:
- cd <!property.workspace>
- echo "현재 디렉토리: $(pwd)"
- ls -la
setMessage: <@property.shellOutput>
# 외부 셸 스크립트 파일 실행
- command: ShellFile
id: shell-script
param:
path: <!property.workspace>/scripts/deploy.sh
args:
- <!property.version>
- prod

View file

@ -1,10 +1,9 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:oto_cli/framework/log/log.dart';
import 'package:oto_cli/framework/platform/process.dart';
import 'package:oto_cli/framework/model/app_data.dart';
import 'package:oto_cli/framework/utils/system_util.dart';
enum SCMType { Svn, Git }
@ -22,8 +21,7 @@ class AppDataManager {
_scm = scm;
_workspace = workspace;
_relativeAssetPath = relativeAssetPath;
var c = Completer<AppData>();
var appData = AppData();
final appData = AppData();
appData.name = name;
appData.gitCount = await gitCount();
appData.version = await getVersion();
@ -32,95 +30,76 @@ class AppDataManager {
appData.gitHash = await getHash();
}
await createFile(jsonEncode(appData.toJson()));
c.complete(appData);
return c.future;
return appData;
}
Future<int> gitCount() async {
var shell = StringBuffer();
shell.writeln('git rev-list --all HEAD --all --count');
var process = await ProcessExecutor.start(shell, workspace: _workspace);
final shell = StringBuffer()..writeln('git rev-list --all HEAD --all --count');
final process = await ProcessExecutor.start(shell, workspace: _workspace);
await process.waitForExit();
return dataFutrue(int.parse(process.stdoutArr.last));
return int.parse(process.stdoutArr.last);
}
Future<String> getVersion() async {
var c = Completer<String>();
var number = 0;
var shell = StringBuffer();
switch (_scm) {
case SCMType.Svn:
break;
case SCMType.Git:
shell.writeln('git rev-list --all HEAD --all --count');
var process = await ProcessExecutor.start(shell, workspace: _workspace);
await process.waitForExit();
number = int.parse(process.stdoutArr.last);
break;
if (_scm == SCMType.Git) {
final shell = StringBuffer()..writeln('git rev-list --all HEAD --all --count');
final process = await ProcessExecutor.start(shell, workspace: _workspace);
await process.waitForExit();
number = int.parse(process.stdoutArr.last);
}
final version = numberToVersion(number);
log('#### Version: $version');
return version;
}
var version = numberToVersion(number);
print('#### Version: $version');
c.complete(version);
return c.future;
Future<String> getHash() async {
final shell = StringBuffer()..writeln('git rev-parse HEAD');
final process = await ProcessExecutor.start(shell, workspace: _workspace);
await process.waitForExit();
final hash = process.stdoutArr.last;
log('#### Git Hash: $hash');
return hash;
}
String getBuildTime() {
final date = DateTime.now().toString();
final result = date.substring(0, date.lastIndexOf('.'));
log('#### Build Date: $result');
return result;
}
Future<void> createFile(String json) async {
final dir = Directory('$_workspace/$_relativeAssetPath');
if (!dir.existsSync()) dir.createSync(recursive: true);
await File('$_workspace/$_relativeAssetPath/app_data.json').writeAsString(json);
}
String numberToVersion(int number, {int masterVersion = 0}) {
var arr = number.toString().split('');
var length = arr.length;
final arr = number.toString().split('');
final length = arr.length;
var ver = '';
var versionList = <int>[];
final versionList = <int>[];
for (int i = 0; i < length; ++i) {
if (i != 0 && i % 3 == 0) {
versionList.add(int.parse(ver));
ver = '';
}
ver = arr[length - (i + 1)].toString() + ver;
ver = arr[length - (i + 1)] + ver;
}
if (ver != '') versionList.add(int.parse(ver));
ver = '';
length = 3;
var list = <String>['0', '0', '0'];
for (int i = 0; i < length; ++i) {
var versionValue = versionList.length <= i ? 0 : versionList[i];
var versionStr = i == 2
final list = <String>['0', '0', '0'];
for (int i = 0; i < 3; ++i) {
final versionValue = versionList.length <= i ? 0 : versionList[i];
list[2 - i] = i == 2
? (versionValue + masterVersion).toString()
: versionValue.toString();
list[length - (i + 1)] = versionStr;
}
return list.join('.');
}
double versionToNumber(String version) {
var value = version.replaceAll('.', '');
return double.parse(value);
}
Future<String> getHash() async {
var shell = StringBuffer();
shell.writeln('git rev-parse HEAD');
var process = await ProcessExecutor.start(shell, workspace: _workspace);
await process.waitForExit();
var hash = process.stdoutArr.last;
print('#### Git Hash: $hash');
return dataFutrue(hash);
}
String getBuildTime() {
var date = DateTime.now().toString();
date = date.substring(0, date.lastIndexOf('.'));
print('#### Build Date: $date');
return date;
}
Future createFile(String json) async {
var c = Completer();
var dir = Directory('$_workspace/$_relativeAssetPath');
if (!dir.existsSync()) dir.createSync(recursive: true);
var file = File('$_workspace/$_relativeAssetPath/app_data.json');
await file.writeAsString(json);
c.complete();
return c.future;
return double.parse(version.replaceAll('.', ''));
}
}