[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CBS905S4282R7K8ZAMV0PV":3,"skill-same:01M6CBS905S4282R7K8ZAMV0PV":45},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":20,"linkOnly":21,"markdownContent":22,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":13,"repositoryHtmlUrl":27,"repositoryHomepageUrl":6,"ownerHtmlUrl":28,"ownerBlogUrl":6,"ownerDescription":29,"updatedAt":30,"install":31,"resources":44},"01M6CBS905S4282R7K8ZAMV0PV","50-editor-window",null,"devwinsoft\u002Fdevian","devwinsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F22570160?v=4","01M6C6RR8W05NH8NPKQNCE3Q72","maoshy","01M6C6RRDJ5TJJCZXRTY801YDH",0,"MIT","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F50-mobile-package\u002F70-build-operation\u002F50-editor-window\u002FSKILL.md",[],[18,19],"editor","50 editor window","2026-11-10 10:12:30",false,"# 50-editor-window — Build Operation GUI\n\nStatus: ACTIVE\nAppliesTo: v11\n\nUnity EditorWindow로 구현하는 빌드 및 릴리스 운영 GUI.\n설정 편집, 빌드 실행, 릴리스(버전 게시 + 심볼 업로드), 실시간 로그를 하나의 창에서 제공한다.\n\n---\n\n## Window 구조\n\nEditorWindow는 4개 탭으로 구성한다:\n\n```\n┌──────────────────────────────────────────────────┐\n│ [Settings] [Pipeline] [Release] [Log] ● Running  │\n├──────────────────────────────────────────────────┤\n│                                                  │\n│              (탭별 내용, ScrollView)               │\n│                                                  │\n└──────────────────────────────────────────────────┘\n```\n\n메뉴 접근: `Devian > Build Automation`\n\n---\n\n## Tab 1: Settings\n\n`BuildAutomationSettings` ScriptableObject를 인라인 편집한다.\n각 섹션은 `EditorStyles.helpBox` VerticalScope GroupBox로 시각적으로 묶는다.\n\n```\n┌─ Settings ───────────────────────────────────────┐\n│                                                  │\n│  ┌─ General ─────────────────────────────────┐   │\n│  │ Build Output Dir  [Builds          ] [...] │   │\n│  └───────────────────────────────────────────┘   │\n│                                                  │\n│  ┌─ Android ─────────────────────────────────┐   │\n│  │ ☐ Include ARMv7                            │   │\n│  │ Keystore Path     [                ] [...] │   │\n│  │ Firebase App ID   [1:xxx:android:xx]       │   │\n│  └───────────────────────────────────────────┘   │\n│                                                  │\n│  ┌─ iOS ─────────────────────────────────────┐   │\n│  │ Firebase App ID   [1:xxx:ios:xxxxx]        │   │\n│  └───────────────────────────────────────────┘   │\n│                                                  │\n│  ┌─ Release ────────────────────────────────┐   │\n│  │ Remote CDN URL    [https:\u002F\u002Fd3...net]      │   │\n│  │ Release Repo Root [              ] [...] │   │\n│  │ AOS Version JSON  [release\u002Fv_aos ] [...] │   │\n│  │ iOS Version JSON  [release\u002Fv_ios ] [...] │   │\n│  └───────────────────────────────────────────┘   │\n│                                                  │\n│  ┌─ Addressables ──────────────────────────────┐ │\n│  │ Exclude Groups  [▼ Select Group      ] [+]  │ │\n│  │   ● Built In Data                           │ │\n│  │   ● LocalStaticAssets                       │ │\n│  │                              [Clear All]    │ │\n│  └─────────────────────────────────────────────┘ │\n│                                                  │\n│  ┌─ CLI Paths ───────────────────────────────┐   │\n│  │ 비워두면 자동 탐색. 'which firebase'로 확인  │   │\n│  │ Firebase CLI  [                ]    [...]  │   │\n│  └───────────────────────────────────────────┘   │\n│                                                  │\n│  ── Prerequisites ──                             │\n│  ✅ Firebase SDK                                 │\n│  ✅ Firebase Android App ID                      │\n│  ✅ Firebase iOS App ID                          │\n│  ✅ google-services.json                         │\n│  ✅ GoogleService-Info.plist                     │\n│  ✅ Firebase CLI  → \u002Fusr\u002Flocal\u002Fbin\u002Ffirebase      │\n│  ✅ Build Output Dir                             │\n│  ✅ Android Module                               │\n│  ✅ iOS Module                                   │\n│  ✅ Addressables                                 │\n│  [Refresh]                                       │\n│                                                  │\n└──────────────────────────────────────────────────┘\n```\n\nPrerequisites 상태는 `OnEditorUpdate`에서 10초 간격으로 갱신한다 (OnGUI 내부 금지).\n플랫폼 모듈 미설치 시 해당 항목은 `— (모듈 미설치)`로 표시 (`DrawStatusRow(string, bool?)` null 오버로드).\n\n---\n\n## Tab 2: Pipeline\n\n2개 섹션으로 구성: Version Info \u002F Build.\nSettings\u002FPipeline\u002FRelease 탭은 공용 `_mainScroll` ScrollView로 감싼다.\n\n**Version Info SSOT**: `PlayerSettings.bundleVersion` (Application prefab의 serialized field가 아님).\n`LoadVersion()`이 `PlayerSettings.bundleVersion`을 `VersionNumber.TryParse()`로 파싱하여 편집 필드에 채운다.\n`ApplyVersion()`은 `PlayerSettings.bundleVersion`에 직접 기록한다.\n\n```\n┌─ Pipeline ───────────────────────────────────────┐\n│                                                  │\n│  ── Version Info ──                              │\n│  App Version    [1].[0].[0]                      │\n│  Android Code   [1        ]                      │\n│  iOS Build      [1.0.0    ]                      │\n│  [Apply] [Revert]          Current: 1.0.0        │\n│  ─────────────────────────────────────────────   │\n│                                                  │\n│  ── Build ──                                     │\n│  ☑ Android  ☑ iOS  (readiness 기반 editable)      │\n│  ☑ Build App Bundle (AAB)                        │\n│  ☑ Build Addressable Bundles                     │\n│  ☐ Development Build (Debug Mode)                │\n│  [▶  Build]                                      │\n│                                                  │\n│  ── (실행 중일 때만) ──                            │\n│  ████████████░░░░░░  75%  Building...            │\n│  [■  Cancel]                                     │\n│                                                  │\n└──────────────────────────────────────────────────┘\n```\n\n### 플랫폼 Readiness\n\n플랫폼 토글은 3단계 개념으로 분리한다:\n\n| 개념 | 변수 | 판정 기준 | 용도 |\n|------|------|-----------|------|\n| Module Supported | `_androidSupported` \u002F `_iosSupported` | `BuildPipeline.IsBuildTargetSupported()` | Version 섹션 DisabledScope, Prerequisites 표시 |\n| Build Ready | `_androidReady` \u002F `_iosReady` | supported AND `firebaseAppIdSet` | 토글 editable 여부 결정 |\n| Build Selected | `_buildAndroid` \u002F `_buildIos` | 사용자가 체크 (ready일 때만 editable) | 실제 빌드 실행 대상 |\n\n- Ready가 아닌 플랫폼: 토글 `false` + `DisabledScope` (읽기 전용)\n- Ready인 플랫폼: 토글 editable, 초기값 `true`\n- `RefreshPrerequisites()`에서 readiness 재계산. not ready로 바뀌면 `_buildAndroid = false` 강제.\n\n### 실행 로직\n\n**Build App Bundle (AAB) 체크박스**: `EditorUserBuildSettings.buildAppBundle`을 토글한다. 스냅샷 `_guiAppBundle` 사용. `_guiBuildAndroid`가 false이면 비활성화. 실행 중에는 비활성화.\n\n**Development Build 체크박스**: `settings.developmentBuild`를 토글한다. 스냅샷 `_guiDevBuild` 사용. 실행 중에는 비활성화.\n\n**Build Addressable Bundles 체크박스**: `_buildAddressables` 토글. 체크 시 앱 빌드 전에 `BundleBuildRunner.Run()` 호출. 실행 중에는 비활성화.\n\n**Build 버튼**: `EditorApplication.delayCall`로 OnGUI 밖에서 `RunBuild()` 실행 (OnGUI 내 BuildPlayer 호출 시 GUI 레이아웃 에러 방지).\n\n`RunBuild()` 실행 흐름:\n1. Phase 1 (메인 스레드, 동기): `_buildAddressables`가 true이면:\n   a. `BundleBuildRunner.Run(excludedGroups)` — 번들 빌드\n   b. 성공 시 `BundleUploadRunner.Run(settings, ct)` — 빌드 산출물을 release repo에 복사 + git commit\n   c. Bundle Copy 실패 시 경고 로그만 출력하고 앱 빌드는 계속 진행\n2. Phase 2 (메인 스레드, 동기): `_buildAndroid`\u002F`_buildIos`가 true인 플랫폼만 앱 빌드\n3. 빌드 성공 시 `AutoFillSymbolPaths()`로 심볼 경로 자동 입력\n\n---\n\n## Tab 3: Release\n\n3개 섹션으로 구성: Version Publish \u002F Bundle Upload \u002F Symbol Upload.\n\n```\n┌─ Release ────────────────────────────────────────┐\n│                                                  │\n│  ── Version Publish ──                           │\n│                                                  │\n│   ── Android ──                                  │\n│   Last Published  current: 1.2.3  min: 1.0.0     │\n│   Current Version [1.2.3      ]                  │\n│   Min Version     [1.0.0      ]                  │\n│   [▶  Publish]                                   │\n│                                                  │\n│   ── iOS ──                                      │\n│   Last Published  current: 1.2.3  min: 1.0.0     │\n│   Current Version [1.2.3      ]                  │\n│   Min Version     [1.0.0      ]                  │\n│   [▶  Publish]                                   │\n│  ─────────────────────────────────────────────   │\n│                                                  │\n│  ── Bundle Upload ──                             │\n│  Release Repo Root  ..\u002Frelease-repo              │\n│  Remote Groups: 3개 감지                          │\n│  [⬆  Upload Bundles to Git]                     │\n│  ─────────────────────────────────────────────   │\n│                                                  │\n│  ── Symbol Upload ──                             │\n│  Android Symbols  [\u002Fabs\u002Fpath\u002Fsymbols.zip] [...]  │\n│  iOS dSYMs        [\u002Fabs\u002Fpath\u002FdSYMs      ] [...]  │\n│  [⬆  Upload Symbols]                            │\n│                                                  │\n│  ── (실행 중일 때만) ──                            │\n│  ████████████░░░░░░  75%  Uploading...           │\n│  [■  Cancel]                                     │\n│                                                  │\n└──────────────────────────────────────────────────┘\n```\n\n### Version Publish 실행 로직\n\n**Version JSON 경로**: Settings 탭의 `BuildAutomationSettings`에 저장. Release 탭에서는 읽기 전용 표시.\n\n**플랫폼별 독립 Publish**:\n- Last Published: JSON에서 읽은 현재 저장값 (readonly). `RefreshLastVersions()`로 갱신.\n- Current Version \u002F Min Version: 편집 가능 TextField (`_editVersionAOS`\u002F`_editVersionIOS`)\n- **Publish 버튼**: 항상 활성화 (실행 중이거나 JSON 경로 미설정 시만 비활성). 플랫폼 readiness와 무관.\n- 각 플랫폼 Publish 버튼 → `RunVersionPublish(platform)`:\n  1. editInfo vs lastInfo 비교하여 변경 여부 판정\n  2. `UpdateVersionJson(path, VersionCheckConfig)` — currentVersion, minVersion 업데이트\n  3. 변경 없으면 → commit 스킵, 로그만 출력\n  4. 변경 있으면 → `GitRunner.Commit()` — git add → commit (push 하지 않음)\n  5. commit message: title + body (변경 전후 currentVersion, minVersion 기록)\n\n### Bundle Upload 실행 로직\n\n**Remote Groups 표시**: `AddressableAssetSettingsDefaultObject.Settings.groups`에서 Remote group 수를 감지하여 읽기 전용 표시.\n\n**Upload Bundles to Git 버튼** (`RunBundleUpload()`):\n1. `BundleUploadRunner.Run(settings, ct)` 호출\n2. Remote group의 빌드 산출물을 `releaseRepoRoot\u002Fv{bundleVersion}\u002F{BuildTarget}\u002F`에 복사\n3. `GitRunner.Commit()`으로 git add → commit (push 하지 않음)\n\n### Symbol Upload 실행 로직\n\n**Upload Symbols 버튼** (`RunSymbolUploadOnly()`):\n1. 심볼 경로 필드가 비어있으면 에러 로그\n2. 경로가 지정되어 있으면 `SymbolUploader.UploadAndroid\u002FUploadIOS`에 절대 경로 전달\n\n### 심볼 경로 관리\n\n- `_androidSymbolPath` \u002F `_iosSymbolPath`: EditorWindow 런타임 상태\n- 빌드 성공 후 `AutoFillSymbolPaths()`가 `buildOutputDir`에서 자동 탐색\n- `[...]` 버튼으로 파일\u002F폴더 수동 선택 가능 (Android: 파일, iOS: 폴더)\n\n---\n\n## Tab 4: Log\n\n실시간 빌드\u002F릴리스 로그 표시. 자체 ScrollView 사용 (메인 ScrollView와 별도).\n\n```\n┌─ Log ────────────────────────────────────────────┐\n│  [Clear]  [Copy All]     Filter: [__________]    │\n│                                  ☑ Auto-scroll   │\n│                                                  │\n│  [15:30:01] [INFO] === Build started ===         │\n│  [15:30:02] [INFO] Android Build...              │\n│  [15:30:45] [INFO] Build succeeded. 45.2 MB      │\n│  [15:30:46] [INFO] symbol path auto-filled: ...  │\n│  [15:31:00] [INFO] === Publish Version ===       │\n│  [15:31:02] [INFO] git commit done               │\n│                                                  │\n└──────────────────────────────────────────────────┘\n```\n\n---\n\n## IMGUI 스냅샷 패턴\n\nIMGUI는 Layout → Repaint 두 패스를 실행한다.\nasync continuation이 두 패스 사이에 상태를 변경하면 컨트롤 수 불일치로 `EndLayoutGroup` 에러가 발생한다.\n\n**해결**: Layout 패스에서 컨트롤 수에 영향을 주는 모든 상태를 스냅샷으로 캡처하고, OnGUI 전체에서 스냅샷만 사용한다.\n\n```csharp\n\u002F\u002F Layout 패스에서 캡처\nif (Event.current.type == EventType.Layout)\n{\n    _guiIsRunning = _isRunning;\n    _guiPhaseLabel = _currentPhaseLabel;\n    _guiProgress = _progress;\n    _guiTab = _currentTab;\n    _guiPrereqStatus = _prereqStatus;\n    _guiVersionDirty = _versionDirty;\n    _guiDevBuild = _settings != null && _settings.developmentBuild;\n    _guiAppBundle = EditorUserBuildSettings.buildAppBundle;\n    _guiAndroidReady = _androidReady;\n    _guiIosReady = _iosReady;\n    _guiBuildAndroid = _buildAndroid;\n    _guiBuildIos = _buildIos;\n    _guiBuildAddressables = _buildAddressables;\n    _guiRemoteGroupCount = _remoteGroupCount;\n    _guiLastVersionAOS = _lastVersionAOS;\n    _guiLastVersionIOS = _lastVersionIOS;\n    _guiEditVersionAOS = _editVersionAOS;\n    _guiEditVersionIOS = _editVersionIOS;\n}\n```\n\n조건부 컨트롤은 항상 동일한 수를 그린다 (else 분기에서 빈 컨트롤 사용).\n\n---\n\n## BuildAutomationLogger\n\n스레드 안전한 중앙 로그 시스템.\n\n- 타입: `BuildLogLevel` (Info\u002FWarning\u002FError), `BuildLogEntry`\n- 메인 스레드: `_entries`에 직접 추가\n- 백그라운드 스레드: `_pendingEntries` 큐에 추가 → `FlushPendingEvents()`에서 메인 스레드로 이동\n- EditorWindow는 `EditorApplication.update`에서 `FlushPendingEvents()` 호출 후, 로그 수 변경 시 `Repaint()`\n- 로그 스냅샷: Layout 패스에서 `_logSnapshot` 배열로 복사하여 Repaint 중 안정성 보장\n\n---\n\n## 파일 목록\n\n```\nSamples~\u002FMobilePackage\u002FEditor\u002FBuildAutomation\u002F\n├── BuildAutomationSettings.cs         # ScriptableObject (10-settings)\n├── BuildAutomationSettingsEditor.cs   # Custom Editor (helpBox GroupBox, 경로 브라우저)\n├── BuildAutomationUtil.cs             # 공통 유틸 (Settings 로드, CLI 해석, Prerequisites)\n├── BuildAutomationLogger.cs           # 로그 시스템 (스레드 안전)\n├── BuildAutomationWindow.cs           # EditorWindow 본체 (4탭)\n├── AndroidBuildRunner.cs              # Build - Android\n├── IOSBuildRunner.cs                  # Build - iOS\n├── BuildReportAnalyzer.cs             # Build - 리포트 분석\n├── BundleBuildRunner.cs               # Pipeline - Addressable 번들 빌드\n├── BundleUploadRunner.cs              # Release - 번들 git 업로드\n├── GitRunner.cs                       # Release - git CLI wrapper\n└── SymbolUploader.cs                  # Release - Symbol Upload\n```\n\n---\n\n## Related\n\n- [00-overview](..\u002F00-overview\u002FSKILL.md) — 그룹 개요\n- [10-settings](..\u002F10-settings\u002FSKILL.md) — BuildAutomationSettings 정의\n- [20-build](..\u002F20-build\u002FSKILL.md) — Build 로직\n- [30-symbol-upload](..\u002F30-symbol-upload\u002FSKILL.md) — Symbol Upload\n- [21-bundle-build](..\u002F21-bundle-build\u002FSKILL.md) — Bundle Build\n- [22-bundle-upload](..\u002F22-bundle-upload\u002FSKILL.md) — Bundle Upload\n- [40-version-publish](..\u002F40-version-publish\u002FSKILL.md) — Version Publish\n",{},"main","8cb5bd877a3a7004867d2f55697cb9e4ff2f1734","skills\u002Fdevian-unity\u002F50-mobile-package\u002F70-build-operation\u002F50-editor-window","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft","Kim Hyong Joon","2026-04-12 18:44:10",{"source":7,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add devwinsoft\u002Fdevian --skill 50-editor-window",{"tool":37,"command":38},"bunx","bunx skills add devwinsoft\u002Fdevian --skill 50-editor-window",{"tool":40,"command":41},"pnpm","pnpm dlx skills add devwinsoft\u002Fdevian --skill 50-editor-window","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian-unity\u002F50-mobile-package\u002F70-build-operation\u002F50-editor-window\u002FSKILL.md",true,[],[46,52,60,68,77,84],{"skillNo":47,"name":48,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":49,"categories":50,"keywords":51,"publishedAt":20},"01M6CBX3K2JJV38ANHVGCAT8A0","devian","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002FSKILL.md",[],[48],{"skillNo":53,"name":54,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":20},"01M6CBX210BFMM8AMN770J9WY8","90-project-archive","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F90-project-archive\u002FSKILL.md",[],[58,59],"archive","90 project archive",{"skillNo":61,"name":62,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":20},"01M6CBWZSGRR9R3YJZB3177667","30-env-management","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F30-env-management\u002FSKILL.md",[],[66,67],"management","30 env management",{"skillNo":69,"name":70,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":20},"01M6CBWYD3TGWBQV7V35551THF","19-page-push-send","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F19-page-push-send\u002FSKILL.md",[],[74,75,76],"push","page","19 page push send",{"skillNo":78,"name":79,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":82,"publishedAt":20},"01M6CBWWRQ05A7604GHJKTPJTF","18-page-savedata","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F18-page-savedata\u002FSKILL.md",[],[75,83],"18 page savedata",{"skillNo":85,"name":86,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":89,"publishedAt":20},"01M6CBWVH4S2VF1V7B2GBTESCT","17-page-deobfuscate","https:\u002F\u002Fgithub.com\u002Fdevwinsoft\u002Fdevian\u002Fblob\u002Fmain\u002Fskills\u002Fdevian\u002F80-tools\u002F20-operation\u002F17-page-deobfuscate\u002FSKILL.md",[],[75,90],"17 page deobfuscate"]