Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
9666f63
Feature: Workflow CLI 기반과 Review 게시 구현
Jeong-Rae Sep 13, 2026
f5f0ef0
Feature: Review Thread와 Stack 동기화 구현
Jeong-Rae Sep 13, 2026
c04f690
Docs: Workflow CLI 구현 아키텍처 확정
Jeong-Rae Sep 13, 2026
44ffe02
Build: Kotlin Workflow CLI 프로젝트 구성
Jeong-Rae Sep 13, 2026
18ea245
Feature: Workflow Domain 규칙 구현
Jeong-Rae Sep 13, 2026
45368da
Feature: Native 친화 JSON 처리 구현
Jeong-Rae Sep 13, 2026
b2bb71c
Chore: Workflow CLI 빌드 스크립트 포맷 적용
Jeong-Rae Sep 13, 2026
93faaf9
Feature: Workflow JSON 결과 계약 구현
Jeong-Rae Sep 13, 2026
14966c6
Feature: Workflow 상태 Aggregate 구현
Jeong-Rae Sep 13, 2026
608d969
Feature: Workflow 외부 포트 계약 구현
Jeong-Rae Sep 13, 2026
dbadcf2
Feature: Workflow 실패 코드 계약 확장
Jeong-Rae Sep 13, 2026
5081bea
Docs: Workflow 라이브러리 선택 기준 추가
Jeong-Rae Sep 14, 2026
a8a6397
Refactor: Workflow 직렬화를 라이브러리로 전환
Jeong-Rae Sep 14, 2026
a8c61ed
Docs: Workflow Git CLI 연동 원칙 명시
Jeong-Rae Sep 14, 2026
19c7609
Test: Workflow 테스트를 Kotest로 전환
Jeong-Rae Sep 14, 2026
e514adf
Feature: Okio Workflow 저장소 어댑터 구현
Jeong-Rae Sep 14, 2026
2e2cc92
Feature: Workflow Application 유스 케이스 구현
Jeong-Rae Sep 14, 2026
9b4f827
Feature: Clikt Workflow 명령 어댑터 구현
Jeong-Rae Sep 14, 2026
a518606
Feature: 로컬 Git CLI 어댑터 구현
Jeong-Rae Sep 14, 2026
ed9327d
Docs: Workflow Domain과 Adapter KDoc 한글화
Jeong-Rae Sep 14, 2026
003b735
Docs: Workflow Application KDoc 한글화
Jeong-Rae Sep 14, 2026
2d6d050
Docs: Workflow Review KDoc 한글화
Jeong-Rae Sep 14, 2026
d5ba2ad
Docs: Workflow 빌드 주석 정리
Jeong-Rae Sep 14, 2026
6eef2e0
Feature: 로컬 검증 어댑터 추가
Jeong-Rae Sep 14, 2026
f956949
Feature: 배포와 공개 Gate 유스케이스 추가
Jeong-Rae Sep 14, 2026
1808554
Fix: Sync 충돌 복구 상태를 영속화
Jeong-Rae Sep 14, 2026
aca7d9e
Feature: 리뷰 Gate 유스케이스 추가
Jeong-Rae Sep 14, 2026
b71b96e
Feature: Review 게시 생명주기 구현
Jeong-Rae Sep 17, 2026
4e9db79
Feature: Workflow ID를 Store에서 발급
Jeong-Rae Sep 17, 2026
3a6fd34
Feature: 배포 후보 생명주기 구현
Jeong-Rae Sep 17, 2026
08ea4c7
Feature: 외부 공개 생명주기 구현
Jeong-Rae Sep 17, 2026
f2a22d6
Feature: Merge Queue 통합 생명주기 구현
Jeong-Rae Sep 17, 2026
617e76f
Feature: GitHub CLI 어댑터 연결
Jeong-Rae Sep 20, 2026
d6bf4a4
Feature: 로컬 Workflow 어댑터 구현
Jeong-Rae Sep 20, 2026
0c76a71
Docs: AI Review 미지원 범위 명시
Jeong-Rae Sep 20, 2026
2b298ba
Build: Native CLI 실행 계약 검증
Jeong-Rae Sep 20, 2026
0937d8e
Feature: Store 기반 SubTask ID 발급
Jeong-Rae Sep 20, 2026
de6a09b
Feature: Stack 동기화와 Git 정리 계약 확장
Jeong-Rae Sep 20, 2026
572e81e
Feature: Merge 이후 안전 정리 구현
Jeong-Rae Sep 20, 2026
9099ab9
Feature: 외부 Workflow 이벤트 처리
Jeong-Rae Sep 20, 2026
1bea927
Feature: GitHub Issue Task 어댑터 추가
Jeong-Rae Sep 20, 2026
39ed845
Test: Sync 충돌 JSON 계약 검증
Jeong-Rae Sep 20, 2026
95b7b78
Feature: Workflow CLI 런타임 연결
Jeong-Rae Sep 20, 2026
02143cd
Test: Workflow CLI 명령 계약 보강
Jeong-Rae Sep 20, 2026
c710d10
Feature: 외부 배포 CLI 어댑터 추가
Jeong-Rae Sep 20, 2026
a426190
Feature: 외부 공개 CLI 어댑터 추가
Jeong-Rae Sep 20, 2026
8b918eb
Feature: Feature Flag CLI 어댑터 추가
Jeong-Rae Sep 20, 2026
4c33a44
Feature: 배포 상태 이벤트 재개
Jeong-Rae Sep 20, 2026
1f5511c
Fix: Git 정리와 Worktree 경계 강화
Jeong-Rae Sep 20, 2026
4d6f8fb
Feature: 외부 배포 런타임 연결
Jeong-Rae Sep 20, 2026
853de9b
Docs: 외부 Provider와 Native 실행 계약 추가
Jeong-Rae Sep 20, 2026
77a4031
Fix: Git ref 입력 검증 강화
Jeong-Rae Sep 21, 2026
2a102e2
Fix: Merge 이후 정리 재시도 보강
Jeong-Rae Sep 21, 2026
769c945
Fix: 배포와 Cleanup 실패 정보 보존
Jeong-Rae Sep 21, 2026
f5551c4
Docs: 외부 이벤트 ingress 경계 명시
Jeong-Rae Sep 21, 2026
fe26cfc
Fix: CLI 인자 오류 JSON 계약 보강
Jeong-Rae Sep 21, 2026
f3550dd
Fix: Review 변경 상태 영속화
Jeong-Rae Sep 21, 2026
a6f6120
Feature: Feature Flag별 Release 생성
Jeong-Rae Sep 21, 2026
7a15dc0
Feature: 누적 배포 후보 선택
Jeong-Rae Sep 22, 2026
27403b3
Fix: Delivery Gate 검증 강화
Jeong-Rae Sep 22, 2026
afa3103
Fix: GitHub Review thread 식별자 보존
Jeong-Rae Sep 22, 2026
e2e86b5
Fix: Release 생성 revision 재동기화
Jeong-Rae Sep 22, 2026
ffde81b
Fix: Review 상태 차단 계약 통합
Jeong-Rae Sep 22, 2026
3c4aa21
Feature: 배포와 공개 이벤트 진행 연결
Jeong-Rae Sep 22, 2026
355e185
Fix: CI 조회 revision 계약 수정
Jeong-Rae Sep 22, 2026
be7315f
Test: 런타임 GitHub 계약 보강
Jeong-Rae Sep 22, 2026
87cd87f
Refactor: 본문 파일 읽기를 Okio Adapter로 분리
Jeong-Rae Sep 22, 2026
af0995f
Fix: Review provider revision 분리
Jeong-Rae Sep 23, 2026
378cc81
Test: 조건과 결과 중심 이름 정리
Jeong-Rae Sep 23, 2026
509dba4
Build: Workflow Native 바이너리 설치
Jeong-Rae Sep 23, 2026
8a7a20d
Docs: Provider revision 계약 명시
Jeong-Rae Sep 23, 2026
6560fa0
Docs: Workflow 런타임 구성 명시
Jeong-Rae Sep 23, 2026
3fda3b6
Docs: 외부 Task 동기화 범위 정리
Jeong-Rae Sep 24, 2026
7085319
Fix: 일반 Review thread lifecycle 연결
Jeong-Rae Sep 24, 2026
981cb60
Fix: Merge Queue revision 동기화
Jeong-Rae Sep 24, 2026
f0d83e2
Fix: 병합 후 정리 재시도 영속화
Jeong-Rae Sep 24, 2026
52ed9fc
Fix: 런타임 구성 오류 JSON 계약 통합
Jeong-Rae Sep 24, 2026
defba12
Fix: PR 본문 템플릿 검증
Jeong-Rae Sep 25, 2026
16849d4
Fix: 일반 Review 작성자와 답글 재시도 판별
Jeong-Rae Sep 25, 2026
f530b3b
Fix: Review 답글 요청 식별자 안정화
Jeong-Rae Sep 25, 2026
396540e
Fix: 병합 후 정리 Git SHA 재시도 검증
Jeong-Rae Sep 25, 2026
0cb2c33
Fix: GitHub 답글 재시도와 상태 검사 복구
Jeong-Rae Sep 25, 2026
5222b7d
Fix: provider SHA와 코드 fingerprint 경계 강화
Jeong-Rae Sep 25, 2026
b296517
Test: 런타임 fixture를 Review 계약에 정렬
Jeong-Rae Sep 25, 2026
074d03d
Build: Workflow Native 바이너리 갱신
Jeong-Rae Sep 26, 2026
ca8adff
Fix: 필수 검증과 원격 게시 가능성 확인
Jeong-Rae Sep 26, 2026
40a647b
Fix: 실제 GitHub Merge Queue 규칙 확인
Jeong-Rae Sep 26, 2026
df4f253
Fix: 코드 줄 코멘트에 GitHub SHA 사용
Jeong-Rae Sep 26, 2026
cbefb5e
Docs: check의 상태 기록과 게시 검사 범위 명시
Jeong-Rae Sep 26, 2026
a0856a6
Build: 최신 Workflow Native 바이너리 반영
Jeong-Rae Sep 26, 2026
0bfe20a
Refactor: GitHub 검사 상태 변환 통합
Jeong-Rae Sep 26, 2026
c3b3888
Refactor: 트랜잭션 조회 결과 분기 명확화
Jeong-Rae Sep 26, 2026
c3c6432
Refactor: Worktree 문맥을 한 번에 조회
Jeong-Rae Sep 26, 2026
36b4263
Refactor: Git 게시 가능성 검사를 종료 상태로 단순화
Jeong-Rae Sep 26, 2026
9bccff8
Build: 단순화한 Workflow Native 바이너리 갱신
Jeong-Rae Sep 26, 2026
a5581f4
Refactor: 런타임 이벤트 조립 책임 분리
Jeong-Rae Sep 26, 2026
9c6b1b5
Build: 이벤트 분리 후 Native 바이너리 갱신
Jeong-Rae Sep 26, 2026
27ead3a
Docs: PR 본문 템플릿 등록
Jeong-Rae Sep 27, 2026
9439755
Fix: Kotlin Workflow CLI PR 리뷰 의견 반영
Jeong-Rae Sep 27, 2026
b249809
Build: Workflow Native 실행 파일 재생성
Jeong-Rae Sep 27, 2026
c3fb857
Fix: Agent 댓글 작성자 분류 우선순위 수정
Jeong-Rae Sep 27, 2026
4c7da7a
Refactor: Workflow CLI 기능 경계와 계정 검증 정리
Jeong-Rae Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/pull_request_template.md
Comment thread
Jeong-Rae marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## 해결하려는 문제

> 어떤 문제를 해결하는지 적습니다. 필요에 따라 사진이나 링크를 첨부하고, 어떤 문제를 마주했는지에 집중해서 설명합니다.

## 필요성

> 문제를 해결해야 하는 이유를 적습니다. 어떤 불편이 생기는지, 왜 문제로 다뤄야 하는지 설명합니다.

## 해결 방법

> 문제를 어떤 방식으로 해결하는지 작성합니다. 설계부터 코드, 테스트까지 엔지니어링 영역에서 고려한 내용을 편안하게 설명합니다.

## 한계와 트레이드오프

> 해결하지 못해 추가 작업이 필요하거나 함께 고려했지만 선택하지 않은 내용을 공유합니다. 리뷰 과정에서 선택지와 그에 따른 영향을 폭넓게 검토할 수 있도록 적습니다.

## 기존 기능에 미치는 영향

> 기존 기능과 달라지는 부분이나 영향을 줄 수 있는 변경을 적습니다.

## 예외 및 실패 사례

> 아직 해결하지 못한 예외나 실패 사례가 있다면 작성합니다. 해결하지 못한 사항도 드러내어 이후 개선할 목표로 이어질 수 있게 합니다.
46 changes: 46 additions & 0 deletions tools/workflow/MANUAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Workflow 명령줄 도구

Workflow CLI(Command line interface, 명령줄 인터페이스)는 저장소 루트에서 실행합니다.

```sh
tools/workflow/bin/workflow <명령> [인자와 옵션]
```

## 명령

| 명령 | 필수 입력 | 주요 옵션과 동작 |
| --- | --- | --- |
| `start` | `--task <task-id>`, `--request-id <request-id>`, `--title <text>` | `--requires <subtask-id>`로 직접 선행 작업을 지정합니다. |
| `check` | 없음 | 현재 Worktree 변경을 검증합니다. |
| `review open` | `--body-file <경로>`, `--risk` (`normal` 또는 `high`) | 변경을 게시하고 초안 PR(Pull request)을 엽니다. |
| `review show` | 없음 | `--diff`, `--threads` (`open` 또는 `all`)를 지원합니다. 기본 조회 범위는 `open`입니다. |
| `review update` | `--revision <revision>` | 변경 커밋을 다시 게시합니다. `--body-file <경로>`를 지정하면 PR 설명도 갱신합니다. |
| `review comment` | `--revision <revision>`, `--level` (`R`, `C` 또는 `A`), `--body <내용>` 또는 `--body-file <경로>` | 본문 입력은 하나만 지정합니다. 인라인 댓글에는 `--path <파일>`과 `--line <번호>`를 함께 지정합니다. |
| `review reply` | `--revision <revision>`, `--thread <thread-id>`, `--body <내용>` 또는 `--body-file <경로>` | 본문 입력은 하나만 지정합니다. |
| `review resolve` | `--revision <revision>`, `--thread <thread-id>` | 지정한 리뷰 대화를 해결 상태로 바꿉니다. |
| `stack` | `--requires <subtask-id>` 또는 `--clear` | 두 옵션 중 하나만 지정합니다. |
| `sync` | 없음 | `--continue`로 동기화를 계속하거나 `--abort`로 중단합니다. 두 옵션은 함께 쓸 수 없습니다. |
| `status` | 없음 | 대상 조회에는 `--subtask`, `--task`, `--candidate`, `--release` 중 하나를 지정합니다. |
| `gate ready <subtask-id>` | `--review-revision <revision>` | PR을 사람 검토 대상으로 전환합니다. |
| `gate approve <subtask-id>` | `--change-revision <revision>` | 코드 변경을 승인합니다. |
| `gate deploy <candidate-id>` | `candidate-id` 인자 | Production Canary 배포를 승인합니다. |
| `gate release <release-id>` | `release-id` 인자 | 외부 공개를 승인합니다. |

각 작업 명령에 `--json`을 붙이면 결과를 구조화된 JSON으로 출력합니다.

`--allow-account-mismatch`는 `review update`, `review comment`, `review reply`, `review resolve`, `gate ready`, `gate approve`, `stack`, `sync`에서 사용할 수 있습니다. PR 변경 전에 현재 `gh` 사용자와 PR 작성자를 비교하며, 다르면 기본적으로 중단합니다. 이 옵션으로 계정 차이만 허용하면 작업을 계속하고 경고를 표준 오류(stderr)에 출력합니다. `--json` 결과는 표준 출력(stdout)에 유지됩니다.

```sh
tools/workflow/bin/workflow start --task SK-33 --request-id sk-33-start --title '문서 추가'
tools/workflow/bin/workflow review open --body-file /tmp/pr-body.md --risk normal
tools/workflow/bin/workflow review show --diff --threads all --json
tools/workflow/bin/workflow review comment --revision abc123 --level C --body '[Agent] 검토 의견입니다.'
tools/workflow/bin/workflow review update --revision rv-4 --allow-account-mismatch --json
tools/workflow/bin/workflow status --subtask sk-33
```

## 댓글과 인증

에이전트가 `review comment` 또는 `review reply`로 작성하는 댓글 본문은 `[Agent]`로 시작해야 합니다. `--body`와 `--body-file` 모두 같은 규칙을 적용합니다.

Workflow CLI는 설정된 인증을 사용하며 로그인이나 인증 절차를 다시 실행하지 않습니다.
Binary file added tools/workflow/bin/workflow
Binary file not shown.
84 changes: 84 additions & 0 deletions tools/workflow/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import io.springkit.workflow.build.WorkflowNativeSmokeTest

plugins {
kotlin("jvm") version "2.4.20"
kotlin("plugin.serialization") version "2.4.20"
application
id("com.diffplug.spotless") version "8.10.2"
id("org.graalvm.buildtools.native") version "1.1.14"
}

group = "io.springkit"

version = "0.1.0-SNAPSHOT"

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

repositories {
mavenCentral()
}

application {
mainClass.set("io.springkit.workflow.MainKt")
}

dependencies {
implementation("com.github.ajalt.clikt:clikt:5.0.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
implementation("com.squareup.okio:okio:3.18.1")

testImplementation("io.kotest:kotest-assertions-core:6.2.4")
testImplementation("io.kotest:kotest-runner-junit5:6.2.4")
testImplementation("com.squareup.okio:okio-fakefilesystem:3.18.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

kotlin {
jvmToolchain(21)
}

spotless {
isEnforceCheck = false

kotlin {
target("src/*/kotlin/**/*.kt", "buildSrc/src/**/*.kt")
ktfmt("0.63").metaStyle()
}

kotlinGradle {
target("*.gradle.kts", "buildSrc/*.gradle.kts")
ktfmt("0.63").metaStyle()
}
}

tasks.withType<Test>().configureEach {
useJUnitPlatform()
}

graalvmNative {
binaries {
named("main") {
imageName = "workflow"
buildArgs.add("--initialize-at-build-time=kotlin.DeprecationLevel")
}
}
}

val installWorkflowNative =
tasks.register<Copy>("installWorkflowNative") {
dependsOn("nativeCompile")
from(layout.buildDirectory.dir("native/nativeCompile")) {
include("workflow")
}
into(layout.projectDirectory.dir("bin"))
}

tasks.register<WorkflowNativeSmokeTest>("workflowNativeSmokeTest") {
dependsOn(installWorkflowNative)
repositoryRoot.set(layout.projectDirectory.dir("../.."))
executable.set(layout.projectDirectory.file("bin/workflow"))
}
7 changes: 7 additions & 0 deletions tools/workflow/buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
`kotlin-dsl`
}

repositories {
mavenCentral()
}
Comment thread
Jeong-Rae marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package io.springkit.workflow.build

import groovy.json.JsonSlurper
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.charset.StandardCharsets
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations

/*
* Native 바이너리의 시작과 JSON 결과 계약을 확인하는 최소 실행 검증입니다.
*/
abstract class WorkflowNativeSmokeTest : DefaultTask() {
@get:Inject abstract val execOperations: ExecOperations

/*
* 실행 위치와 환경 변수에만 사용하므로 입력 fingerprint에서 제외한 저장소 루트입니다.
*/
@get:Internal abstract val repositoryRoot: DirectoryProperty

/*
* 시작과 JSON 결과 계약을 검증할 Native 실행 파일입니다.
*/
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val executable: RegularFileProperty

@TaskAction
fun verifyNativeBinary() {
val repositoryRootFile = repositoryRoot.get().asFile
val executableFile = executable.get().asFile
val stateFile = temporaryDir.resolve("state.json")
stateFile.writeText(
"""
{
"schema_version": 1,
"store_revision": 0,
"tasks": {
"smoke-task": {
"id": "smoke-task",
"external_id": {"value": "SMOKE-1"},
"title": "Native smoke task",
"state": "OPEN",
"sub_task_ids": []
}
}
}
"""
.trimIndent()
)

val help = invoke(executableFile, repositoryRootFile, stateFile, "--help")
check(help.exitCode == 0) {
"Native 바이너리의 --help 실행이 실패했습니다. 종료 코드: ${help.exitCode}\n${help.stderr}"
}
check(help.stdout.contains("Usage: workflow")) {
"Native 바이너리가 도움말을 출력하지 않았습니다.\n${help.stdout}"
}

val success =
invoke(
executableFile,
repositoryRootFile,
stateFile,
"status",
"--task",
"smoke-task",
"--json",
)
requireJsonResult(success, expectedType = "success", expectedExitCode = 0)

val failure =
invoke(
executableFile,
repositoryRootFile,
stateFile,
"status",
"--subtask",
"missing-subtask",
"--json",
)
val failureJson = requireJsonResult(failure, expectedType = "failure", expectedExitCode = 1)
val failureData = failureJson["data"] as? Map<*, *>
check(failureData?.get("code") == "SUBTASK_NOT_FOUND") {
"대표 JSON 실패 코드가 예상과 다릅니다.\n${failure.stdout}"
}

val configurationFailure =
invoke(
executableFile,
repositoryRootFile,
stateFile,
"status",
"--json",
extraEnvironment =
mapOf(
"WORKFLOW_DEPLOYMENT_COMMAND" to "[\"deploy\"]",
"WORKFLOW_RELEASE_COMMAND" to "",
),
)
val configurationJson =
requireJsonResult(configurationFailure, expectedType = "failure", expectedExitCode = 1)
val configurationData = configurationJson["data"] as? Map<*, *>
check(configurationData?.get("code") == "INVALID_ARGUMENT") {
"구성 오류가 공통 JSON 실패로 변환되지 않았습니다.\n${configurationFailure.stdout}"
}

val actorConfigurationFailure =
invoke(
executableFile,
repositoryRootFile,
stateFile,
"status",
"--json",
extraEnvironment = mapOf("WORKFLOW_ACTOR_KIND" to "HUMAN"),
)
val actorConfigurationJson =
requireJsonResult(actorConfigurationFailure, expectedType = "failure", expectedExitCode = 1)
val actorConfigurationData = actorConfigurationJson["data"] as? Map<*, *>
check(actorConfigurationData?.get("code") == "INVALID_ARGUMENT") {
"주체 종류 구성 오류가 공통 JSON 실패로 변환되지 않았습니다.\n${actorConfigurationFailure.stdout}"
}
}

private fun invoke(
executable: File,
repositoryRoot: File,
stateFile: File,
vararg arguments: String,
extraEnvironment: Map<String, String> = emptyMap(),
): NativeInvocation {
val stdout = ByteArrayOutputStream()
val stderr = ByteArrayOutputStream()
val result = execOperations.exec {
commandLine(executable.absolutePath, *arguments)
workingDir(repositoryRoot)
environment("WORKFLOW_REPO_ROOT", repositoryRoot.absolutePath)
environment("WORKFLOW_STATE_FILE", stateFile.absolutePath)
extraEnvironment.forEach { (name, value) -> environment(name, value) }
standardOutput = stdout
errorOutput = stderr
isIgnoreExitValue = true
}
return NativeInvocation(
exitCode = result.exitValue,
stdout = stdout.toString(StandardCharsets.UTF_8.name()),
stderr = stderr.toString(StandardCharsets.UTF_8.name()),
)
}

private fun requireJsonResult(
invocation: NativeInvocation,
expectedType: String,
expectedExitCode: Int,
): Map<*, *> {
check(invocation.exitCode == expectedExitCode) {
"JSON 경로의 종료 코드가 다릅니다. " +
"기대값: $expectedExitCode, 실제값: ${invocation.exitCode}\n${invocation.stderr}"
}
val lines = invocation.stdout.lineSequence().filter(String::isNotBlank).toList()
check(lines.size == 1) {
"JSON 경로는 표준 출력에 JSON 객체 하나만 기록해야 합니다.\n${invocation.stdout}"
}
val json = lines.single().trim()
val parsed = JsonSlurper().parseText(json) as? Map<*, *>
check(parsed != null) { "JSON 경로가 객체를 출력하지 않았습니다.\n$json" }
check(parsed["type"] == expectedType) {
"JSON 결과 type이 다릅니다. 기대값: $expectedType\n$json"
}
check(parsed["data"] is Map<*, *>) {
"JSON 결과에 data가 없습니다.\n$json"
}
return parsed
}

private data class NativeInvocation(
val exitCode: Int,
val stdout: String,
val stderr: String,
)
}
3 changes: 3 additions & 0 deletions tools/workflow/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
org.gradle.configuration-cache=true
org.gradle.parallel=true
kotlin.code.style=official
7 changes: 7 additions & 0 deletions tools/workflow/gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions tools/workflow/gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tools/workflow/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = "workflow"
Loading