Compare commits
10 commits
be1b139c25
...
c7e0e5ffc6
| Author | SHA1 | Date | |
|---|---|---|---|
| c7e0e5ffc6 | |||
| 93c3643fc3 | |||
| 11d462b45c | |||
| a2a10cafe5 | |||
| 6e918f3aad | |||
| eddf4e421a | |||
| 9ffcf5f4d6 | |||
| d4e02592a8 | |||
| 4aaa5d900c | |||
| 33bd6ee57f |
26 changed files with 1531 additions and 80 deletions
10
.claude/settings.json
Normal file
10
.claude/settings.json
Normal 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
16
.cursorrules
Normal 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
1
.gitignore
vendored
|
|
@ -57,3 +57,4 @@ release/
|
|||
assets/data/app_data.json
|
||||
.DS_Store
|
||||
agent-ops/rules/private/
|
||||
.claude/settings.local.json
|
||||
|
|
|
|||
374
README.md
374
README.md
|
|
@ -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 지정 가능
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1.1.3
|
||||
1.1.7
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
73
agent-ops/rules/project/domain/sample/rules.md
Normal file
73
agent-ops/rules/project/domain/sample/rules.md
Normal 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 사용)
|
||||
- 존재하지 않는 커맨드나 파라미터를 샘플에 넣지 않는다
|
||||
|
|
@ -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` 읽고 해당 샘플 참조 |
|
||||
|
|
|
|||
|
|
@ -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` |
|
||||
|
|
|
|||
36
agent-ops/skills/common/sync-pull/SKILL.md
Normal file
36
agent-ops/skills/common/sync-pull/SKILL.md
Normal 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 를 거치지 않고 직접 파일을 복사하지 않는다
|
||||
|
|
@ -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 경로 입력을 안내하고, 입력받은 경로로 실행
|
||||
|
||||
37
assets/yaml/sample/01_basic.yaml
Normal file
37
assets/yaml/sample/01_basic.yaml
Normal 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>"
|
||||
53
assets/yaml/sample/02_pipeline_if.yaml
Normal file
53
assets/yaml/sample/02_pipeline_if.yaml
Normal 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: "신규 플로우 실행"
|
||||
60
assets/yaml/sample/03_pipeline_foreach.yaml
Normal file
60
assets/yaml/sample/03_pipeline_foreach.yaml
Normal 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>"
|
||||
46
assets/yaml/sample/04_pipeline_exe_handle.yaml
Normal file
46
assets/yaml/sample/04_pipeline_exe_handle.yaml
Normal 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: "아티팩트 업로드 중..."
|
||||
66
assets/yaml/sample/05_pipeline_while_switch.yaml
Normal file
66
assets/yaml/sample/05_pipeline_while_switch.yaml
Normal 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: "대기중"
|
||||
76
assets/yaml/sample/06_git.yaml
Normal file
76
assets/yaml/sample/06_git.yaml
Normal 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>
|
||||
85
assets/yaml/sample/07_file.yaml
Normal file
85
assets/yaml/sample/07_file.yaml
Normal 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>
|
||||
67
assets/yaml/sample/08_notification.yaml
Normal file
67
assets/yaml/sample/08_notification.yaml
Normal 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>
|
||||
80
assets/yaml/sample/09_network.yaml
Normal file
80
assets/yaml/sample/09_network.yaml
Normal 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"
|
||||
104
assets/yaml/sample/10_build_ios.yaml
Normal file
104
assets/yaml/sample/10_build_ios.yaml
Normal 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>"
|
||||
55
assets/yaml/sample/11_build_flutter.yaml
Normal file
55
assets/yaml/sample/11_build_flutter.yaml
Normal 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> 빌드 완료"
|
||||
40
assets/yaml/sample/12_scheduler.yaml
Normal file
40
assets/yaml/sample/12_scheduler.yaml
Normal 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: "나이틀리 빌드 완료"
|
||||
96
assets/yaml/sample/13_string_json.yaml
Normal file
96
assets/yaml/sample/13_string_json.yaml
Normal 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>"
|
||||
61
assets/yaml/sample/14_shell_process.yaml
Normal file
61
assets/yaml/sample/14_shell_process.yaml
Normal 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
|
||||
|
|
@ -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('.', ''));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue