Compare commits

...
3 Commits
Author SHA1 Message Date
jychoi4 d7a533672a docs: add beginner Microsoft Store publishing manual
Cover account preparation, app identity, package submission, updates and troubleshooting, including current builder limitations.

Closes #8
2026-11-03 14:00:00 +09:00
jychoi4 388e1e3327 feat: implement Installer Studio MSIX authoring workflow
Add the shared Rust engine, Tauri UI and CLI, registry authoring, developer signing, branded start screen and .isproj persistence. Remove the unused project ID.

Validation: frontend build and tests, Rust workspace tests, native build and real Windows SDK package generation passed.

Closes #2
Closes #3
Closes #4
Closes #5
Closes #6
Closes #7
2026-11-02 15:12:00 +09:00
jychoi4 24b624301c docs: define MSIX-first packaging design and roadmap
Document package formats, release workflows, deployment planning and UI concepts.

Closes #1
2026-10-27 16:30:00 +09:00
145 changed files with 20764 additions and 66 deletions

No files matched your search

+8
View File
@@ -6,6 +6,9 @@
*.pfx
*.p12
*.key
*.isproj.local.json
*.isproj.bak
*.isproj.local.json.bak
# Build dependencies and temporary files
node_modules/
@@ -15,3 +18,8 @@ __pycache__/
*.log
.DS_Store
Thumbs.db
# Local sample payloads, build workspaces and generated MSIX packages
/output/samples/
.mcp.json
+6
View File
@@ -0,0 +1,6 @@
{
"recommendations": [
"ms-vscode.cpptools",
"rust-lang.rust-analyzer"
]
}
+84
View File
@@ -0,0 +1,84 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "MSIX Studio: Desktop (Rust)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/src/target/vscode/debug/msix-studio.exe",
"cwd": "${workspaceFolder}/src/apps/desktop/src-tauri",
"args": [],
"environment": [{ "name": "RUST_BACKTRACE", "value": "1" }],
"console": "integratedTerminal",
"stopAtEntry": false,
"preLaunchTask": "MSIX: build desktop (debug)"
},
{
"name": "MSIX CLI: Schema",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/src/target/vscode/debug/package-builder.exe",
"cwd": "${workspaceFolder}/src",
"args": ["schema"],
"environment": [{ "name": "RUST_BACKTRACE", "value": "1" }],
"console": "integratedTerminal",
"stopAtEntry": false,
"preLaunchTask": "MSIX: build CLI (debug)"
},
{
"name": "MSIX CLI: Validate project",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/src/target/vscode/debug/package-builder.exe",
"cwd": "${workspaceFolder}/src",
"args": ["validate", "--project", "${input:projectPath}"],
"environment": [{ "name": "RUST_BACKTRACE", "value": "1" }],
"console": "integratedTerminal",
"stopAtEntry": false,
"preLaunchTask": "MSIX: build CLI (debug)"
},
{
"name": "MSIX CLI: Plan release",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/src/target/vscode/debug/package-builder.exe",
"cwd": "${workspaceFolder}/src",
"args": ["plan", "release", "--project", "${input:projectPath}", "--target", "${input:targetId}"],
"environment": [{ "name": "RUST_BACKTRACE", "value": "1" }],
"console": "integratedTerminal",
"stopAtEntry": false,
"preLaunchTask": "MSIX: build CLI (debug)"
},
{
"name": "MSIX CLI: Build plan",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/src/target/vscode/debug/package-builder.exe",
"cwd": "${workspaceFolder}/src",
"args": ["build", "--project", "${input:projectPath}", "--plan-id", "${input:planId}"],
"environment": [{ "name": "RUST_BACKTRACE", "value": "1" }],
"console": "integratedTerminal",
"stopAtEntry": false,
"preLaunchTask": "MSIX: build CLI (debug)"
}
],
"inputs": [
{
"id": "projectPath",
"type": "promptString",
"description": "프로젝트 JSON 경로 (절대 경로 또는 src 기준 상대 경로, 따옴표 없이 입력)",
"default": "fixtures/InstallerProject.isproj"
},
{
"id": "targetId",
"type": "promptString",
"description": "프로젝트에 정의된 출력 대상 ID",
"default": "msix-direct"
},
{
"id": "planId",
"type": "promptString",
"description": "Plan release 결과의 id (실행하지 않은 새 계획의 UUID)"
}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"rust-analyzer.linkedProjects": ["src/Cargo.toml"],
"files.watcherExclude": {
"**/target/**": true,
"**/node_modules/**": true,
"**/.work/**": true
}
}
+58
View File
@@ -0,0 +1,58 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "MSIX: install frontend dependencies",
"type": "shell",
"command": "npm.cmd",
"args": ["ci", "--no-audit", "--no-fund"],
"options": {
"cwd": "${workspaceFolder}/src/apps/desktop",
"shell": { "executable": "cmd.exe", "args": ["/d", "/c"] }
},
"problemMatcher": []
},
{
"label": "MSIX: build frontend",
"type": "shell",
"command": "npm.cmd",
"args": ["run", "build"],
"options": {
"cwd": "${workspaceFolder}/src/apps/desktop",
"shell": { "executable": "cmd.exe", "args": ["/d", "/c"] }
},
"dependsOn": "MSIX: install frontend dependencies",
"dependsOrder": "sequence",
"problemMatcher": []
},
{
"label": "MSIX: build desktop (debug)",
"type": "process",
"command": "cargo",
"args": ["build", "--locked", "-p", "msix-studio", "--features", "custom-protocol", "--target-dir", "target/vscode"],
"options": { "cwd": "${workspaceFolder}/src" },
"dependsOn": "MSIX: build frontend",
"dependsOrder": "sequence",
"problemMatcher": [],
"group": { "kind": "build", "isDefault": true }
},
{
"label": "MSIX: build CLI (debug)",
"type": "process",
"command": "cargo",
"args": ["build", "--locked", "-p", "package-builder", "--target-dir", "target/vscode"],
"options": { "cwd": "${workspaceFolder}/src" },
"problemMatcher": [],
"group": "build"
},
{
"label": "MSIX: test Rust",
"type": "process",
"command": "cargo",
"args": ["test", "--locked", "--workspace", "--target-dir", "target/vscode"],
"options": { "cwd": "${workspaceFolder}/src" },
"problemMatcher": [],
"group": { "kind": "test", "isDefault": true }
}
]
}
+21 -5
View File
@@ -1,15 +1,31 @@
# EG-BIM Network Setup
# Installer Studio — Windows 설치 패키지 제작기
EG-BIM의 Windows 설치기 전환, 네트워크 배포 및 버전별 패치 관리를 위한 계획과 설계 저장소입니다.
여러 Windows 제품의 설치 패키지를 만드는 Rust + Tauri 제작기입니다. 현재 개발 버전은 **MSIX 생성·레지스트리 작성·개발자 서명**을 지원합니다. EXE 제작, 배포 서버 연결 및 Store 제출용 출력은 후속 단계이며, EG-BIM은 적용 사례입니다.
- [프로그램 실행·빌드·검증 안내](src/README.md)
- 프로젝트 파일은 `.isproj`만 지원하며 내부 형식은 JSON v2입니다. 별도의 프로젝트 ID는 사용하지 않습니다.
## 현재 설계와 시안
- [MSIX 우선 제작기 설계](docs/code-design/12-msix-first-builder.md)
- [프로그램 설계서 — Word](docs/program-design/MSIX-프로그램-설계서.docx): 참고 설계서 양식에 맞춘 6개 모듈·18개 기능·6개 화면과 검증·일정.
- [MSIX 제작기 디자인 미리보기](output/msix-builder-preview.html): 프로젝트 작업실형·단계별 제작형, 설정 검사와 모의 빌드.
- [MSIX 프로젝트 예제](docs/code-design/examples/msix.isproj)
## 문서
- [초보자를 위한 Microsoft Store 배포 매뉴얼](docs/manuals/microsoft-store/README.md): 개발자 계정, 앱 등록, MSIX 준비, 심사와 단계별 배포.
- [설치 및 배포 계획](docs/plan/README.md)
- [Rust + Tauri 제작 프로그램 설계](docs/code-design/README.md)
- [설치기 디자인 미리보기](output/egbim-installer-preview.html): 파일을 브라우저에서 열어 확인합니다.
- [설치기 디자인 원본](output/egbim-installer-concepts.html)
- [계획 검토 결과](docs/plan/09-plan-review.md)
- [범용 제작기와 EXE·MSIX 출력 설계](docs/code-design/11-generic-exe-msix-architecture.md)
- [구현 백로그 — 1차 18개·후속 35개](docs/backlog/README.md)
- [단독 개발 일정과 마일스톤 — AI 지원 없음](docs/plan/10-development-schedule.md)
- [EG-BIM EXE 설치 마법사 미리보기](output/egbim-installer-wizard-preview.html): EXE 출력의 참고 테마이며, MSIX의 OS 설치 화면과는 별개입니다.
- [설치 과정·UI 재설계](docs/code-design/10-installer-wizard-redesign.md)
- [초기 설치기 시안](output/egbim-installer-preview.html): 비교 자료로 보존합니다.
현재는 계획·설계와 인터랙티브 시안 단계입니다. 시안은 실제 설치나 시스템 변경을 수행하지 않습니다.
`src`의 프로그램은 실제 Windows SDK를 사용해 MSIX를 생성합니다. `output`의 HTML은 별도 디자인 시안이며 실제 설치나 시스템 변경을 수행하지 않습니다. 개발용 패키지 생성·검증 성공은 일반 배포 및 대상 PC 설치 시험 완료를 의미하지 않습니다.
## 로컬 설정
+171
View File
@@ -0,0 +1,171 @@
# 범용 설치 패키지 제작기 구현 백로그
작성일: 2026-10-19. [검토 결과](../plan/09-plan-review.md)와 [단독 개발 일정](../plan/10-development-schedule.md)을 함께 사용한다. 백로그는 저장소 문서로만 관리하며 Gitea 이슈는 생성하지 않는다.
## 현재 범위 — MSIX 우선, 기준선 v3
첫 제품은 **MSIX 생성·서명·직접 배포 업데이트 설정·제작 GUI/CLI**다. EXE/MSI/MSP·HTML 설치 마법사·EG-BIM 전환·자체 그룹 배포 서버·Store 제출은 후속으로 이월한다. [현재 설계](../code-design/12-msix-first-builder.md), [변경 기록](../changes/2026-10-19-msix-first.md) 참조.
기존 53개 ID는 유지하며 **1차 18개 / 후속 35개**로 구분한다. 모든 구현 작업은 미착수다. 시안은 구현 완료 증빙이 아니다. 표의 P0/P1은 해당 개발 단계 안의 우선순위이며 후속 P0가 첫 MSIX 출시를 막지 않는다.
| 개발 단계 | 백로그 ID | 완료 기준 |
| --- | --- | --- |
| 1차 MSIX | BL-02, BL-03, BL-06, BL-09~11, BL-14, BL-16, BL-29, BL-41, BL-46~53 | MS0~MS6. 두 독립 제품의 MSIX 설치/업데이트/제거까지 검증 |
| 후속 EXE·전환·배포 | BL-01, BL-04~05, BL-07~08, BL-12~13, BL-15, BL-17~28, BL-30~40, BL-42~45 | 후속 일정 미배정. 기존 요구를 보존하되 완료 처리하지 않음 |
BL-02/03/06의 WiX·타사 EG-BIM PE·그룹 배포 추가 검증은 후속 관련 작업으로 넘긴다. BL-10의 EXE v1 가져오기는 후속 EXE 단계의 인수 기준이다. BL-50에서 공통 서명·불변 저장을 먼저 구현하고 BL-19/20에서 EXE 전용 순서와 기준 저장소를 확장한다. BL-53은 현재 **MSIX 제품 완료 게이트**이며 두 형식 통합 회귀는 후속 BL-17/28에서 추가한다.
## 운영 규칙
- **인력/산정 기준:** 개발자 1명, AI 코딩·설계·테스트·문서화 지원 없음. 개발자가 시험과 배포 운영 준비도 수행한다. 외부 승인자·테스터는 추가 개발 인력이 아니다.
- **우선순위:** P0는 선행 위험 해소·데이터 보존·배포 차단 조건, P1은 해당 단계 완성의 필수 기능, P2는 선택적 확장이다. 1차 P1을 임의로 제외해 첫 제품 완료로 표시하지 않는다.
- **상태:** 아래 모든 항목은 `미착수`. 이후 `진행 중 → 검증 중 → 완료`로 갱신한다. `차단`은 원인·필요 자료·해제 조건을 함께 기록한다. 시안과 설계 작성은 구현 완료 증빙이 아니다.
- **선행 조건:** 표의 ID는 완료 후 착수하는 기본 순서다. 인터페이스 검토·자료 수집은 먼저 할 수 있지만 선행 구현의 시험을 통과한 것으로 취급하지 않는다. 외부 전제는 ID 외에도 명시했다.
- **작업 한도:** 구현 항목은 동시에 1개만 진행한다. 외부 답변 대기 중에는 선행 조건이 충족된 다른 항목을 선택한다. 큰 항목은 착수 전에 1~5일 작업으로 나누되 부모 ID와 완료 기준을 유지한다.
- **공통 완료 기준:** 변경 코드/문서, 의미 있는 자동 검사, 필요한 Windows VM 실험, 실패 경로, 로그/비밀정보 점검, 사용 절차를 연결한다. 실행하지 않은 검사는 `notRun`으로 남기며 완료를 선언하지 않는다. 시험 기록은 아티팩트 해시·기준 상태·도구/OS·기대/실제 결과를 포함한다.
- **외부 자료:** 실제 제품 바이너리·고객 설정·서명 키는 공개 Git 저장소에 올리지 않는다. 승인된 별도 저장 위치와 식별자·해시·접근 절차만 연결한다.
현재 마일스톤은 `MS0~MS6`다. 이전 `M0~M10/M-MSIX` 일정은 대체했으며 후속 작업의 과거 단계 이름으로 사용하지 않는다. 담당은 별도 표시가 없으면 단독 개발자이며, 계약·제품 승인·테스터 참여는 관련 외부 주체의 협조가 필요하다. 작업별 예상 일수는 M0에서 실측 자료를 확보한 뒤 세분화한다.
## A. 기준 확정과 위험 실험 — 기준
근거: [현황](../plan/01-requirements-and-current-state.md), [전환](../plan/02-installer-migration.md), [패치](../plan/08-version-patching.md), [설치 UI](../code-design/09-install-flow-and-ui.md).
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-01 | P0 | 실제 출시본·payload·런타임 인벤토리 | 실제 배포본/소스 접근 | 지원 후보별 EXE/MSI 해시·GUID·버전·언어·아키텍처·경로와 제거 작업 목록 작성. 실제 배포 파일/사용자 데이터/타사 파일을 구분하고 제조사·런타임 재배포 조건의 확인 여부 기록. |
| BL-02 | P0 | 도구·지원 환경과 MSIX 위험 실험 | 없음 | Windows SDK MakeAppx/SignTool, Rust/Tauri, 최소 Windows/CPU 조합과 도구 해시 고정. 작은 Win32 앱으로 MSIX 생성·서명·실행·업데이트 재현. WiX/Burn은 요구하지 않음. 지원 범위/학습 공수를 MS0에서 재산정. |
| BL-03 | P0 | MSIX 서명·게시자·신뢰 정책 | BL-46 | 개발/운영 서명 프로필, Publisher/Subject 일치, 대상 PC 신뢰, 타임스탬프·갱신·비밀 저장 정책 확정. 실제 운영 서명 접근의 차단 조건 기록. EG-BIM·EXE 서명은 후속. |
| BL-04 | P0 | 지원 기준 상태·전체 대체 정책 | BL-02, BL-46 | 실제 지원할 legacy 목록과 WiX 기준 상태 목록 정의. 신규 설치 상태/패치 도달 상태를 구분하고 보존 기간, Minor/Major 경로, 패치 제거 기본 비활성 정책 문서화. 공통 MSI 기준 상태는 가상 제품으로 확정; EG-BIM legacy 지원 목록은 BL-01 이후 별도 확정. |
| BL-05 | P0 | 서버·장치·기존 채널 계약 | 없음 | 사내망/인터넷, 저장소/DB/인증 방식과 용량 가정 결정. Center/Family/External의 의미, 장치 그룹 충돌·최초 등록·오프라인 라이선스 경로 결정. |
| BL-06 | P0 | MSIX 인수 기준·VM 매트릭스 | BL-02, BL-46 | 두 독립 가상 제품과 지원 OS/CPU/권한별 설치·실행·버전 증가·제거·데이터 보존 기준 확정. 불일치 인증서·누락 의존성·디스크 부족·중단·네트워크 실패와 설치 중 사용 상태 포함. Store·EXE·그룹 테스터 요구 없음. |
| BL-07 | P0 | 기존 제거와 설정 보존 실험 | BL-01, BL-02 | 스냅샷 VM에서 실제 구버전 제거 전후 파일·HKCU/HKLM·CAD 연결 차이를 측정. 다른 계정 UAC·사용자 지정 경로를 포함해 외부 백업의 복원 가능성과 미복구 범위를 기록. |
| BL-08 | P0 | Rust/Burn BA·UI 부트스트랩 실험 | BL-02 | Detect/Plan/Apply·취소·오류·재부팅 이벤트의 최소 연결을 재현. WebView2 없는 오프라인 VM에서 시작 경로 확인. 미성립 시 native 선행 UI 등 대안과 추가 공수를 결정하고 자유 UI 약속을 보류. |
## B. Rust 공통 엔진과 CLI — 공통
근거: [구조](../code-design/01-architecture.md), [데이터](../code-design/02-project-model.md), [파일/레지스트리](../code-design/03-files-and-registry.md), [식별자](../code-design/04-version-and-identity.md), [명령](../code-design/07-ui-and-commands.md).
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-09 | P1 | Rust workspace·Windows CI·fixture | BL-02 | domain/engine/infra/cli 의존 방향과 잠금 파일 구성. 새 환경에서 format/lint/build/test 실행, 소형 가상 제품 준비. 로컬 설정·작업 폴더·대형 산출물을 형상 관리에서 제외. |
| BL-10 | P0 | v2 MSIX 프로젝트·저장·검증 | BL-09, BL-46 | Rust/JSON Schema 일치, 미지 필드/버전·revision 충돌·백업 검증. formats.exe 없이 MSIX 프로젝트 열기/저장. EXE/v1/Store 미지원 입력은 설명과 함께 거부하고 자동 변환하지 않음. 확정되지 않은 identity·서명으로 정식 결과 확정 불가. |
| BL-11 | P0 | 파일 해석과 스냅샷 입력 | BL-10 | 포함/제외·필수 파일·정렬 결과가 결정적. 대소문자 충돌, 경로 탈출/ADS/reparse point, 출력 재수집 차단. 스트림 복사·해시, 복사 중 원본 변경 탐지, 원본 무변경 확인. |
| BL-12 | P0 | 버전·GUID·identity lock | BL-04, BL-10, BL-11 | 제품/패키지/패치/컴포넌트 식별자의 수명·tombstone·예약 이력 구현. MSI 버전 경계 및 PE 표시 버전 매핑 검사. 같은 리소스의 안정적 GUID와 동시 확정 차단 확인. |
| BL-13 | P1 | 레지스트리·설치 위치 컴파일 | BL-10, BL-12 | 타입·뷰·literal/formatted 값을 검증하고 WXS에 안전하게 직렬화. 기존 경로 보존, 소유한 값만 제거, 미지원 HKCU 정책 차단. 생성기 PC 레지스트리가 바뀌지 않음을 확인. |
| BL-14 | P0 | 프로세스·작업·중단 복구 | BL-09, BL-10 | 허용 도구/인자 배열·시간 제한·Job Object·OS 잠금 구현. GUI/CLI 중복 실행 차단, 취소 시 자식 종료, Interrupted 복원, 재개 전 입력/완료 산출물 해시 재검사. |
| BL-15 | P0 | 설치 단계 카탈로그·검증기 | BL-10, BL-13 | 모드/phase/조건 타입·의존 그래프 검증. 순환·필수 백업 삭제·권한 없는 핸들러·미지원 모드 차단. 화면 숨김과 실행 단계 제거를 구분하고 무인 설치 필수 입력 검사. |
| BL-16 | P1 | MSIX 공통 계획 서비스와 CLI | BL-11, BL-14, BL-47 | validate/plan/build/job/export 계약·종료 코드·JSON 출력. 오래된 revision/도구/identity 계획과 중복 요청 차단. 실제 빌드·내보내기는 BL-49/50에서 연결. patch/MSI/EXE/Store 요청은 명시 오류. |
## C. 전체 설치·서명·구버전 전환 — 후속 EXE와 공통 VM
근거: [릴리스](../code-design/05-release-pipeline.md), [전환](../plan/02-installer-migration.md), [서명](../plan/06-code-signing-and-smartscreen.md).
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-17 | P0 | WiX MSI/Burn 전체 설치 생성 | BL-02, BL-16, BL-47 | 실제 MSI 테이블/식별자와 기대 계획 일치. 기본·사용자 지정 경로, 필수 런타임, silent·오프라인·재부팅, repair/uninstall의 소형 제품 시험 통과. CLI build 연결. 직접 배포용 offline-singlefile/offline-layout/network 출력과 불변 payload 검증. 후속 완료 시 두 독립 제품의 EXE와 기존 MSIX 비회귀 확인. |
| BL-18 | P0 | 대용량 CAB·출력 한계 검사 | BL-17 | 실제에 가까운 압축률로 총 2GB 초과 레이아웃 설치/복구 확인. 개별 파일 초기 지원 한계·단일 EXE 크기/서명 불가를 명확히 차단. 임시 공간 포함 계산과 메모리 사용 측정. |
| BL-19 | P0 | 운영 서명·타임스탬프 어댑터 | BL-03, BL-17, BL-50 | 자체/타사 PE 구분, CAB/MSI/Burn 서명 순서와 최종 해시 검증. 서명·타임스탬프 실패 시 배포 불가, 완료 파일 재서명 방지. 실제 운영 서명과 개발 인증서 결과를 분리. BL-50의 공통 서명 포트를 EXE 순서로 확장. |
| BL-20 | P0 | EXE 기준 릴리스·증빙 저장 확장 | BL-18, BL-19, BL-50 | 동일 releaseId 덮어쓰기 차단, MSI/wixpdb/서명 payload/정의/도구/로그 보존. Finalized와 설치 시험 증빙 분리, 결과 상태를 실제 파일에서 추출. 증빙 없는 정식 배포 준비 상태 차단. BL-50의 불변 저장 계약을 재사용. |
| BL-21 | P0 | 사용자별 백업·검증·복원 | BL-14 | 원래 사용자 SID 기준 CUI/설정/추가 리소스 보관 및 호환 설정만 복원. 여러 프로필·다른 관리자 UAC·부분 실패 결과를 호출자에 전달. 임시 다운로드 정리와 백업 수명 분리. 공통 계약은 가상 사용자 데이터로 검증. EG-BIM 실제 보존 정책은 BL-07 검증 후 적용. |
| BL-22 | P0 | 실제 EG-BIM 전환 실행 | BL-15, BL-20, BL-21 | 전체 payload 확보·백업 검증 전 구버전 제거 차단. 지원 legacy별 기존 경로·타사 CAD 연결·사용자 데이터 보존 및 앱 실행 확인. 실패/전원 중단 시 복구 상태·로그·백업 유지. |
| BL-41 | P0 | VM 시험 실행·증빙 수집 기반 | BL-02, BL-09 | 업무 PC와 분리된 지원 OS VM/스냅샷·fixture·로그 수집 구성. 첫 패키지부터 실제 아티팩트 해시와 기준 상태로 증빙 저장. 이후 단계마다 신규/패치/실패 시험 매트릭스를 누적 실행. 기반은 MS1에 준비하고 각 게이트에서 재사용. |
## D. 패치와 적용 실행 계약 — 후속 EXE
근거: [패치 운영](../plan/08-version-patching.md), [패치 생성](../code-design/06-patch-pipeline.md), [검증](../code-design/08-validation-and-roadmap.md).
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-23 | P0 | 기존 업데이터 실행 능력 검증 | BL-02 | NuGet 구현/테스트로 MSI/MSP 실행·승격·서명 검증·종료 코드·재부팅·동시 실행 지원을 확인. 재사용 가능/별도 구현 필요 항목과 API 계약 작성. 조사부터 먼저 착수할 수 있으나 완료는 실제 실행 증빙 필요. 공통 실행 어댑터는 가상 제품으로 검증하며 EG-BIM NuGet 적합성은 BL-01 이후 별도 적용 결과로 기록. |
| BL-24 | P0 | 설치 시도 상태 머신 | BL-21, BL-23 | attemptId·잠금·원래 사용자·3010/1641·재부팅 후 재개·rollback 완료/불명확 상태 구분. 앱 저장/종료 실패 시 적용 차단, 불명확 상태에서 전체 설치 강행 차단. |
| BL-25 | P0 | baseState 저장·diff·호환성 | BL-04, BL-12, BL-20 | ProductCode/PackageCode·언어·아키텍처·패치 이력·관리 파일로 기준 식별. 신규 설치와 패치 도달 상태 분리. 삭제/이동/런타임 구조 변경을 FullUpgradeRequired로 분류하고 사용자 데이터를 비교 대상에서 제외. |
| BL-26 | P0 | MSP 생성·서명·불변 보관 | BL-19, BL-25 | 한 기준 상태→한 목표 MSP 생성 및 실제 PatchCode/family/sequence 검증. 목표 PE 재서명 없음. 중복/역행 패치 차단, 크기 비교, 선언된 기준과 최종 해시 보관. |
| BL-27 | P0 | Burn/MSP 및 전체 대체 경로 | BL-17, BL-24, BL-26 | 패치 후 ARP/Burn/MSI 버전·repair·제거·다음 업데이트 일관성 확인. 같은 ProductCode와 major 전환을 구분하고 source state별 동일 목표 전체 경로 검증. 미검증 경로는 FullUpgradeRouteUnavailable. |
| BL-28 | P0 | 패치 기준별 동등성·실패 시험 | BL-17, BL-26, BL-27, BL-41 | 최소 두 선언 기준 상태에서 목표 신규 설치와 공급자 관리 상태 비교. 중간 버전 생략·중복·파일 수정/누락·원본 캐시 누락·디스크 부족·중단·재부팅·복구/제거/다음 패치 시험. 선언한 모든 기준의 증빙 통과. 공통 게이트는 가상 제품 기준이며 실제 EG-BIM 전환 기준 시험은 BL-22 이후 BL-42에서 확인. 후속 두 형식 설치/업데이트 회귀를 추가하되 MSIX에 MSP 적용 금지. |
## E. Tauri 제작기와 최종 설치 UI — 제작기와 후속 EXE UI
근거: [제작기 UI/명령](../code-design/07-ui-and-commands.md), [설치 과정/UI](../code-design/09-install-flow-and-ui.md).
2026-10-19 [재설계](../code-design/10-installer-wizard-redesign.md)에 따라 아래 작업을 구체화한다. 시안 작성으로 구현 상태는 변경하지 않는다. [변경 기록](../changes/2026-10-19-installer-ui-redesign.md) 참조.
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-29 | P1 | MSIX 제작기 화면·IPC·자체 배포 준비 | BL-16, BL-49 | 앱 정보·파일·패키지·서명·업데이트·검증/빌드 화면과 작업 이력. CLI와 같은 엔진, 작업 재연결, 최소 권한·경로 검증. 제작기 자체 배포 identity는 고객 앱과 분리. 선택한 단일 디자인 구현, WiX/EXE 출력 구현 없이 개발 환경 시작 및 배포/실행 확인. |
| BL-30 | P1 | 설치 과정 편집기 | BL-15, BL-29 | 단계 추가/활성화/허용 순서/조건/등록 핸들러 속성 편집. 필수 단계 삭제와 의존성 오류를 저장/빌드에서 검사, 모드별 실행 계획 표시. 임의 관리자 셸 입력 기능 없음. |
| BL-31 | P1 | 기본 테마·문구·리소스 편집 | BL-17, BL-29 | 고정 템플릿·로고·지원 색상/배치·문구/다국어 편집과 모드 미리보기. 실제 native 출력 비교, 미지원 자유 페이지 기능 명시. 변경 시 새 빌드/서명 필요성 반영. |
| BL-32 | P1 | 사용자 정의 BA·자유 페이지 편집 | BL-08, BL-24, BL-30, BL-31 | 승인된 UI 백엔드로 입력 바인딩·검증·페이지/조건 분기·Feature 선택 구현. Burn 외 중복 설치 실행 없음, 적용 시작 후 계획 고정, 무인 입력 계약과 WebView2 부트스트랩 검증. 기본 테마 기능으로 대체 완료하지 않음. |
| BL-33 | P1 | UI 실물 검증과 접근성 | BL-29~32, BL-41 | DPI·키보드·스크린 리더·다국어 및 오류/취소/실패/재부팅 화면 확인. 미리보기와 서명 번들 차이를 기록. patch/repair의 Feature 유지 및 대화형/무인 설치 결과 동등성 통과. |
### 재설계에 따른 추가 인수 기준
| 작업 | 기존 완료 기준에 추가할 검사 |
| --- | --- |
| BL-15 | welcome/license/options/review/prepare/apply/result 페이지와 실행 그래프를 분리. 업데이트의 약관 버전 조건, 필수 준비 및 무인 동의/입력 계약 검사. |
| BL-24 | 적용 전 취소, 적용 중 취소 요청, 엔진이 확인한 rollback 결과, 상태 불명확, 재부팅 필요를 구분하고 UI에 전달. |
| BL-30 | 모드별 단계 목록·조건/복구 속성과 잠금 규칙 편집. 화면 숨김이 백업/무결성 제거로 이어지지 않음. |
| BL-31 | 진행 안내형/표준 마법사형의 지원 범위 검증. 페이지 목록·미리보기·속성 패널, 로고/문구/지원 스타일 편집. 미지원 native 매핑을 명시. |
| BL-32 | 신규/전환/패치/복구/제거 페이지 분기와 이전 이동 시 입력 유지. review에서 계획 고정, apply의 이전 이동 차단, 제거 확인·약관 동의 차단. 검토용 상황 선택기는 배포 화면에서 제외. |
| BL-33 | 두 외형에서 정상·백업 실패·실행 앱·적용 실패·취소·재부팅을 검증. 백업 전 제거 불가, 상태 불명확 시 무조건 재설치 불가, 재부팅 필요 시 앱 실행 숨김, 종료/재부팅 기본값 확인. |
## F. 서버·업데이터·단계 배포 — 후속 서버
근거: [패키지/업로드](../plan/03-package-and-upload.md), [단계 배포](../plan/04-staged-rollout.md), [패치](../plan/08-version-patching.md).
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-34 | P0 | 내보내기·서버 수신·신뢰 계약 | BL-05, BL-20, BL-25, BL-50 | artifacts/dependsOn·적용 경로·증빙 스키마를 고정하고 모든 CAB/런타임 의존 관계 포함. Finalized/DeploymentReady와 Uploaded/Verified/Ready의 의미를 매핑. 메타데이터 서명·만료·키 교체·다운그레이드/재생 방지 정책과 클라이언트 검증 계약 결정. format별 identity/업데이트 경로를 구별하고 MSIX에 MSP 실행 경로를 제공하지 않음. |
| BL-35 | P1 | 임시 업로드·검증·릴리스 등록 | BL-34 | 중단/재개·해시/서명/누락 검증·중복 릴리스 거부. 검증 실패는 격리, 불변 URL 등록, 준비 증빙 없는 Ready 차단. 업로드/승격 권한 분리와 감사 기록. |
| BL-36 | P0 | 인증·장치 그룹·대상 선택 API | BL-05, BL-35 | 사용자 주장만으로 테스트 그룹 진입 불가, 최초 장치 등록·다중 사용자 충돌 정책 구현. 그룹 목표 결정 후 현재 상태에 맞는 승인 경로 선택. 사용자별 응답 캐시 격리·패키지 다운로드 접근 통제. |
| BL-37 | P0 | 네트워크 설치·업데이터 연결 | BL-24, BL-27, BL-34, BL-36 | WebSetup의 고정 payload 경로와 사용자 목표 선택 책임 분리. 재개/프록시/만료 URL·해시/서명/메타데이터 검증 후 실행. 적용 직전 허가 재확인, 오프라인 라이선스의 전체 설치 경로 시험. 서버 장애 시 기존 앱 사용 유지. |
| BL-38 | P1 | 설치/실행 결과 수집·집계 | BL-06, BL-24, BL-36 | attemptId+이벤트 순서로 중복 제거·오프라인 재전송. 다운로드/취소/재부팅/미응답/실행 실패 별도 집계. 기준→목표별 패치 실패와 전체 복구 성공을 구분하고 개인정보 최소 수집/보존 정책 적용. |
| BL-39 | P0 | 수동 승인·중지·정책 충돌 | BL-06, BL-36~38 | 승인자·관측 증빙·정책 revision으로 4단계 승격. 릴리스 전체 중지와 패치만 중지를 구분하고 미승인 전체 경로 우회 차단. 적용 전 차단/시작 후 강제 종료 없음 확인. 필요 최소 CLI/API 제공, 복잡한 운영 UI는 제외. |
| BL-40 | P1 | 보존·정리·복원 운영 | BL-04, BL-20, BL-35 | 현재/복구/진행 설치/지원 패치의 참조 자료 삭제 차단. 정리 계획 미리보기와 승인 이력, 저장소·메타데이터 백업 복원 시험. 서명 payload와 기준 MSI 누락 탐지. |
## G. 출시 검증과 운영 인수 — 후속 적용
근거: [필수 검증](../plan/07-implementation-and-validation.md), [관측/승격](../plan/04-staged-rollout.md), [개발 일정](../plan/10-development-schedule.md).
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-42 | P0 | 전체 경로 회귀와 4단계 파일럿 | BL-06, BL-28, BL-33, BL-37~41, BL-43 | 실제 legacy→WiX→MSP→후속 업데이트와 설치 후 인증/도면/출력/플러그인 회귀 통과. 개발자→1차→2차→일반 사용자 단계마다 동일 아티팩트 해시·기준별 표본/실행/장애 지표로 승인. 표본 부족/중대 장애 시 보류. |
| BL-43 | P0 | 운영 절차·릴리스 체크리스트 | BL-03, BL-06, BL-22, BL-28, BL-39, BL-40 | 서명 장애·중지·복구·재부팅 대기·지원 문의·데이터 보존 절차 작성 및 모의훈련. 수정 릴리스와 일반 다운그레이드 금지 원칙 반영. 개발자 본인 검증과 외부 업무 사용자 승인 기록 분리. |
BL-42는 후속 EG-BIM 종합 시험부터 일반 공개까지 이어지는 상위 작업이다. 단계별 증빙을 남기며 최종 승격 전에는 항목 전체를 완료 처리하지 않는다. BL-41의 기반 구축은 MS1, 후속 회귀 실행은 각 마일스톤의 시험 시간에 포함한다.
## H. Microsoft Store — 별도 선택 일정
근거: [Store 계획](../plan/05-microsoft-store.md), [출력 프로필](../code-design/05-release-pipeline.md).
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-44 | P2 | 제품별 Store 제출 적합성 판정 | BL-53 | 등록할 제품의 EXE 또는 MSIX 적합성, 계정·권리·정책 확인. EXE는 단일 오프라인/무인/서명, MSIX는 Store identity·capability·의존성 검증. 미지원 제품 전환 비용은 별도 산정. MSIX 출력 구현을 이 작업으로 미루지 않음. |
| BL-45 | P2 | Store 제출·자체 업데이트 연계 | BL-44 | EXE는 고정 HTTPS URL·무인 인자, MSIX는 패키지 업로드·Store identity·Store 업데이트 경로로 제출. 대상 제품의 설치/업데이트 증빙 필수; EG-BIM을 제출할 때만 BL-42 적용 증빙 추가. 실제 제출·심사·공개 상태 구분, 미승인 결과를 공개 완료로 표시하지 않음. |
## I. MSIX 필수 기능 — MS0~MS6
근거: [범용 EXE·MSIX 구조](../code-design/11-generic-exe-msix-architecture.md). 기존 작업의 공통 필드·서명·VM 기반을 재사용하되 EXE 규칙을 MSIX에 강제하지 않는다.
| ID | 우선순위 | 작업 / 산출물 | 선행 조건 | 완료 기준 |
| --- | --- | --- | --- | --- |
| BL-46 | P0 | MSIX 우선 범위·제품 계약 | 없음 | 1차 MSIX/직접 배포, 후속 EXE/Store·HTML UI 경계 확정. format/delivery/distribution 모델은 유지. EG-BIM·Store·자체 인증 서버 없이 가능한 계약과 두 가상 제품 정의. |
| BL-47 | P0 | PackageBackend·MSIX 작업 조정기 | BL-10, BL-14, BL-46 | 1차 MsixBackend 포트와 대상별 검증/계획/결과/잠금 구현. MSIX-only에는 MSI 필드를 요구하지 않음. 직렬 다중 대상의 부분 실패·불변 결과 보존·새 buildId 재시도 검증. EXE/Store는 UnsupportedTarget. |
| BL-48 | P0 | MSIX manifest·identity·호환성 검사 | BL-11, BL-47 | Name/Publisher/4부분 Version/Architecture·최소 OS·실행 항목·자산·의존성·허용 capabilities 검증. MSI GUID 불필요, 임의 설치 경로·미지원 작업과 잘못된 Store identity 차단. |
| BL-49 | P1 | MakeAppx MSIX 생성 백엔드 | BL-02, BL-14, BL-48 | 고정 SDK로 staging→manifest→pack→내용/identity 검사. 검증 우회 금지, 원본 무변경, 누락 파일/자산·취소·디스크 부족 실패 기록. `.msix` 필수, bundle은 후속 범위. |
| BL-50 | P0 | MSIX 서명·불변 결과·내보내기 | BL-03, BL-14, BL-49 | 직접 배포 Publisher/인증서 일치·서명/신뢰·해시 검사. 개발/운영/미서명 상태, Finalized/DeploymentReady 분리. 동일 releaseId 덮어쓰기 금지, manifest·SDK·입력·패키지·로그·보고서 보존/내보내기. 인증서/타임스탬프 실패와 재시도 검증. EXE 저장소·Store 제출 프로필은 후속. |
| BL-51 | P1 | MSIX 업데이트·App Installer | BL-50, BL-41 | 상위 버전 전체 MSIX와 HTTPS App Installer 생성/검증. 파일 안내 URI와 버전별 패키지 URI·서명/identity·OS/스키마 조건 확인. 실행 중 업데이트·재시도·버전 건너뛰기·데이터 보존/제거 시험. Windows 차등 경로와 전체 선다운로드 차이를 기록. 인증/그룹 서버·EXE 전환은 후속. |
| BL-52 | P1 | MSIX 제작 UI·CLI 완성·접근성 | BL-29, BL-48, BL-50 | 1차 MSIX 설정만 노출하고 직접 배포/자동 업데이트 사용 설정. 실제 파일/자산/의존성/서명 프로필 편집과 출력 폴더 선택. 잘못된 버전·Publisher·실행 파일·URI의 원인/위치 안내. DPI·키보드·스크린 리더·취소/중단/재연결·긴 작업 UI·GUI/CLI 동등성 검증. HTML 설치 마법사 불필요. |
| BL-53 | P0 | MSIX 제품 완료·회귀·사용 안내 | BL-06, BL-16, BL-41, BL-48~52 | 두 독립 가상 제품 각각 MSIX 생성·실제 서명·직접 설치·실행·상위 버전 업데이트·제거·앱 데이터 정책 검증. 부분 실패·미서명/개발 결과 제한·원본 무변경·누락 런타임/자산·Publisher 오류 시험. 불변 증빙과 사용자 가이드/운영 절차 제공. EXE·Store·EG-BIM·자체 그룹 서버 없이 MS6 통과. |
## 첫 착수 순서
1. BL-46/02/03/06: MSIX 범위·도구·서명·설치/업데이트 실험과 완료 기준.
2. BL-09/10/11/14/47/16/41: 공통 모델·입력·작업 엔진·CLI·VM 기반.
3. BL-48/49: manifest와 실제 MSIX 생성.
4. BL-50: 서명·불변 결과·직접 배포 내보내기.
5. BL-51: App Installer 업데이트와 실제 적용 검증.
6. BL-29/52: 제작기 GUI와 IPC·실파일 편집·접근성.
7. BL-53: 두 제품 회귀와 MS6 출시 준비.
후속 35개 항목에는 날짜를 배정하지 않는다. 현재 작업의 필수 의존성이 후속 항목을 참조하지 않는지 변경마다 검사한다. 재개할 때 EXE/BA·실제 제품·서버/Store 범위를 다시 산정한다.
@@ -0,0 +1,29 @@
# 범용 제작기 범위 정정과 EXE·MSIX 필수 출력
날짜: 2026-10-19. 상태: 사용자 의도 반영, 설계/백로그 수정 완료. 구현과 실제 패키지 시험은 미착수다.
## 변경 이유와 결정
사용자는 범용 설치 프로그램을 개발하며, 이 프로그램이 만든 EXE나 MSIX를 필요에 따라 Microsoft Store에 등록하려 한다. 기존 문서는 EG-BIM 설치기 전환을 제품 전체의 목적으로 두고 MSIX를 선택 확장으로 분류했다. 이 해석을 바로잡는다.
- 제품은 여러 Windows 앱을 위한 설치 패키지 제작기다. EXE와 MSIX 백엔드를 모두 필수 제공하고 프로젝트별로 하나 또는 둘을 선택한다.
- 출력 형식, 오프라인/네트워크 전달 구성, 직접 배포/Store 배포 대상을 분리한다. 로컬 빌드에 Store 계정·자체 서버·EG-BIM 자료를 요구하지 않는다.
- EXE 설치 경로·마법사·MSP와 MSIX manifest·OS 설치/업데이트를 분리한다. 미지원 입력을 자동 무시하거나 임의 변환하지 않는다.
- EG-BIM 전환·데이터 보존·서버 배포 요구는 적용 트랙에 유지한다. 범용 제품과 적용 완료의 증빙/마일스톤을 구별한다.
## 반영 위치
| 대상 | 반영 |
| --- | --- |
| [범용 출력 계약](../code-design/11-generic-exe-msix-architecture.md) | 책임 경계·두 백엔드·형식별 기능·서명/업데이트·부분 실패·완료 기준 |
| [v2 모델](../code-design/02-project-model.md), [예제](../code-design/examples/generic.package-project.json) | 공통 필드 + formats + targets; 기존 EG-BIM v1 입력은 명시적 가져오기 |
| 구조·파일·identity·릴리스·패치·UI·검증 문서 | 공통 규칙과 EXE 전용 규칙 분리, MSIX 연결 |
| [배포 계획](../plan/README.md), [Store](../plan/05-microsoft-store.md), [검토](../plan/09-plan-review.md) | 패키지 생성과 등록 분리, 적용 사례 범위 명시 |
| [백로그](../backlog/README.md) | 기존 ID 유지, BL-46~53 필수 추가; 총 53개. 공통과 EG-BIM/서버/Store 게이트 분리 |
| [일정 v2](../plan/10-development-schedule.md) | 단독·AI 없음 유지, MSIX 8주 잠정 추가, 범용 W38 / 전체 적용 W52 / 완충 W53~56 |
기존 44주+4주 계획과 MSIX 제외 문구는 일정 v2로 대체한다. [앞선 UI 변경 기록](2026-10-19-installer-ui-redesign.md)의 일정 유지 판단은 당시 범위에 대한 이력이며 이번 정정 이후의 기준선이 아니다. UI 시안은 EG-BIM EXE용 참고 테마로 보존한다.
## 확인과 남은 작업
문서 링크·JSON 구문·백로그 ID/의존성·일정 날짜의 정합성을 확인한다. 예제에는 실제 payload·서명·identity 확정이 필요한 값이 있으며 실행 가능한 구현으로 표시하지 않는다. WiX/MakeAppx 빌드, 서명, Windows 설치 시험, Store 제출은 이번 문서 변경에서 수행하지 않는다. Gitea 이슈는 생성하지 않는다.
@@ -0,0 +1,23 @@
# 설치 과정·UI 재설계 변경 기록
- 요청: InstallShield와 Inno Setup을 참고해 설치 과정과 UI 재디자인.
- 변경일: 2026-10-19.
- 상태: 설계 및 상호작용 시안 작성. 운영 구현·설치 검증 미완료.
- 기준 문서: [재설계](../code-design/10-installer-wizard-redesign.md).
- 비교 자료: [이전 시안](../../output/egbim-installer-preview.html), [새 시안](../../output/egbim-installer-wizard-preview.html).
## 변경 내용
기존 4단계 시안을 시작·약관/선택·최종 확인·준비·적용·완료 구조로 세분화했다. 신규 설치/전환/패치/복구/제거를 구분하고 이전/다음/취소의 동작을 통일했다. 백업 실패·실행 앱·상태 불명확·재부팅 필요 화면과 제거 확인을 추가했다. 제작기의 페이지 목록/미리보기/속성 패널 구성도 문서로 구체화했다.
## 영향과 완료 판정
BL-15, BL-24, BL-30~33의 상세 완료 기준을 갱신한다. 기존 백로그 ID와 미착수 상태를 유지한다. 실행기/패치 계약은 BL-27과 연결한다. 이번 요청은 Gitea 이슈 생성이나 설치 엔진 교체 요청이 아니며 문서만으로 관리한다.
44주+완충 4주 일정은 기준선을 유지하되 기존 범위 내 상세화라고 해서 추가 공수가 없다고 단정하지 않는다. 모드별 화면과 실패 경로의 구현·시험량을 M0와 M4 착수 시 재산정한다. 사용자 정의 BA 필요성을 기본 테마만으로 해결했다고 표시하지 않는다. AI가 없는 단독 개발 산정 조건은 유지한다.
## 확인 범위
공식 자료의 UI 패턴을 확인했고 JavaScript 문법 및 9개 상태 전이 검사를 통과했다. 검사에는 5개 모드의 정상 경로, 약관/경로/제거 확인, 백업 실패, 실행 앱 차단, 적용 실패/취소, 재부팅, 취소 확인 중 전이 차단, 저장 상태 검증을 포함한다. Chromium 렌더링으로 밝은/어두운 테마, 320px 너비, 최종 확인과 백업 실패 화면도 확인했다. 문서 상대 링크와 Git 공백 검사도 통과했다.
실제 Burn/Tauri 실행, Windows UAC, 백업/복원, MSI/MSP 적용과 접근성 보조기기 시험은 후속 백로그다. 시안의 예시 상태를 실제 설치 검증 증빙으로 사용하지 않는다.
+28
View File
@@ -0,0 +1,28 @@
# MSIX 제작기 우선 개발
날짜: 2026-10-19. 사용자 요청에 따라 제품 1차 범위를 MSIX 제작으로 변경했다. 개발자 1명·AI 지원 없음의 산정 조건, 문서 백로그만 관리하는 방침을 유지한다.
## 결정
- 1차: 범용 MSIX 프로젝트·파일/manifest·MakeAppx·서명·불변 결과·직접 배포/App Installer·GUI/CLI·실제 설치/업데이트 검증.
- 후속: EXE/MSI/MSP/Burn, HTML 설치 마법사, 추가 동작 핸들러, 기존 제품 전환, 자체 그룹 배포 서버, Store 제출 프로필/등록, bundle.
- MSIX-only 프로젝트에 MSI GUID·EXE 설정·WiX 도구·Store 계정을 요구하지 않는다.
- 시안 대상은 설치 최종 사용자 화면이 아니라 **패키지를 만드는 데스크톱 프로그램**이다. 프로젝트 작업실/단계별 제작 두 외형은 검토 대안이며 둘 다 구현하는 공수를 자동 추가하지 않는다.
## 반영
[12번 설계](../code-design/12-msix-first-builder.md), [백로그](../backlog/README.md), [일정 v3](../plan/10-development-schedule.md), 주요 문서의 적용 범위와 색인, [MSIX-only 예제](../code-design/examples/msix.package-project.json), [시안](../../output/msix-builder-preview.html)에 반영한다.
기존 BL-01~53은 유지한다. 1차 18개/후속 35개로 구분하고 BL-02/03/06/10/16/29/46~53의 인수 기준과 의존성을 현재 범위에 맞게 수정한다. 범용 서명/저장 기능은 BL-50에서 먼저 만들고 EXE 관련 BL-19/20에서 확장한다. BL-53은 두 독립 제품의 **MSIX** 회귀로 변경한다. 이 변경이 EXE 구현 완료를 의미하지 않는다.
기존 v2의 W38 범용 제작기·W52 전체 적용 일정은 현행 기준이 아니다. v3는 MSIX 24주+완충 4주의 잠정 계획이며 후속 EXE/서버/Store 작업은 일정 미배정이다. 목표 출시일은 사용자 확정 사항이 아니고 MS0에서 도구/서명/지원 범위 실험 후 재산정한다.
## 이번 변경의 검증
문서 29개의 로컬 링크, JSON 구문, 53개 작업 ID와 비순환 의존성, 1차 18개 작업에서 후속 작업으로의 의존성 없음, MS0~MS6 및 완충 날짜를 확인했다. `git diff --check`를 통과했다.
Chrome에서 시안의 메타데이터 반영, 잘못된 버전, 파일 추가/실행 파일 누락, Publisher 불일치/오류 이동, 업데이트 URI, 모의 빌드/로그, 미서명 결과, 설정 변경 후 결과 무효화, 반응형 너비, 실제 시안 전환과 단계 이동을 포함한 11개 검사 묶음을 통과했다. 런타임 페이지 오류는 없었고 밝은/어두운 화면과 좁은 화면을 렌더링해 확인했다. 상태 저장 이벤트가 검증 오류를 지우던 문제는 수정 후 재검증했다.
이 검증은 문서와 시안에 대한 것이다. Rust 컴파일·MakeAppx 실행·서명·MSIX 설치·업데이트·Store 제출은 수행하지 않았다.
[앞선 범용 범위 정정](2026-10-19-general-package-builder.md)은 두 형식을 장기 제품 범위로 다룬 이력이다. 현재 첫 제품 완료에는 EXE를 요구하지 않는다. 이전 시안과 설계는 후속 작업 자료로 보존한다. 구현 작업은 모두 미착수이며 이번 결과는 설계와 모의 UI다.
@@ -0,0 +1,65 @@
# 현재 제작기로 EG-BIM MSIX 샘플 생성
사용자 요청에 따라 InstallShield ISM을 참고하여 현재 MSIX Studio의 입력 프로젝트를 준비하고, GUI와 같은 Rust 엔진을 쓰는 `package-builder` CLI로 생성한다. 별도 스크립트가 MakeAppx를 직접 호출하지 않는다. 제작기 본체의 패키징 코드는 변경하지 않는다.
## 입력과 변환 범위
| 항목 | 사용한 값 |
| --- | --- |
| ISM | `D:\egbim\repo\14.1\InstallShield\src\EG-BIM_V1.13.1.ism` |
| 본체 | `D:\egbim\repo\14.1\Build\EG-BIM\EG-BIM.Pro.64bit` |
| EgViewer | `D:\egbim\repo\14.1\CoWork\Import\EgViewer` |
| 추가 경로 보정 | 같은 저장소의 IntelliCAD 텍스처·brand 아이콘·영문 사전·Tablet 및 CadFonts 리소스 |
| 파일 수 | ISM 4,701개, 누락 0개, 표시 아이콘 2개 추가 |
| 패키지 정보 | `Baron.EGBIM.Sample`, `CN=Baron MSIX Sample`, `1.13.1.0`, x64 |
| 도구 | 현재 CLI + Windows SDK `10.0.26100.0` MakeAppx |
실제 사용 경로는 파일별 CSV/JSON으로 기록한다. 원본을 수정하거나 하드링크하지 않고 선별한 파일을 별도 payload 폴더에 복사해 해시를 대조한다. ISM에 없는 최신 빌드 파일이나 Debug 결과를 자동으로 섞지 않는다.
처음에는 파일별 include 패턴을 입력했으나 현재 제작기의 대량 패턴 검사에서 메모리 사용량이 커져 해당 검사를 중단했다. 최종 프로젝트는 준비된 payload 전체를 읽는 규칙과 아이콘 규칙 두 개를 사용한다. 별도 비교로 ISM 파일의 경로·크기·해시와 계획의 정확한 일치를 확인한다. 제작기 전반의 대량 패턴 처리 개선은 이 샘플 작업에서 구현하지 않았다.
## 실행 경로
1. `cargo build --locked -p package-builder`로 현재 소스의 CLI를 빌드한다.
2. [입력 준비 스크립트](../../src/scripts/Prepare-EgbimMsixSample.py)는 ISM 매핑·payload·프로젝트·미변환 보고서를 만든다.
3. [자산 준비 스크립트](../../src/scripts/New-EgbimSampleAssets.ps1)는 기존 Grimi 아이콘에서 PNG를 준비한다.
4. [CLI 실행 스크립트](../../src/scripts/Build-EgbimMsixSample.ps1)는 현재 제작기의 `plan release`, `build`를 호출하며 `-Export` 지정 시 `export`도 호출한다.
5. 제작기 내부에서 MakeAppx 빌드·역추출·파일 해시와 manifest identity 검사를 수행한다.
로컬 프로젝트는 `D:\networkSetup\output\samples\egbim-msix-20261022\package-project.json`이다. GUI의 프로젝트 열기에서 같은 파일을 선택할 수 있다. 대용량 payload와 산출물은 `.gitignore`의 `/output/samples/`로 제외한다.
작업 ID: `e13d4d28-8271-4beb-8241-060eff762247`. 최종 실행 결과는 로컬 `reports/build-result.json`, 내보내기는 `reports/export-result.json`, 도구 출처는 `reports/builder-provenance.json`에 기록한다.
## 현재 구현의 한계
실제 빌드 결과는 `ArtifactVerified`, 파일 크기는 **1,923,183,886바이트**다. 제작기 내부의 역추출 및 4,703개 입력 파일 해시 비교가 통과했다. 별도 패키지 목록 검사에서도 ISM 파일 4,701개가 모두 존재함을 확인했다. MSIX의 URI 이스케이프된 파트 이름을 복원하여 한글/공백 경로를 비교했다.
SHA-256: `67a151d213d5de52ace80e8a5fe50662f2686dca7e7f5d6cc8b63e022ee3a17c`.
추가 export 작업은 복사·해시 처리 후 OS 호출에서 대기하여 해당 프로세스만 중단했다. 대기 원인은 확정하지 않았다. 결과는 이미 성공한 `releases/e13d4d28-8271-4beb-8241-060eff762247/package.msix`를 사용하며, 빌드 결과를 export 성공으로 표시하지 않는다. 재실행 스크립트에서 export는 선택 옵션으로 두었다.
- 미서명 개발 결과이며 운영 배포·Store 제출용이 아니다. 인증서 신뢰 등록과 설치/실행 시험은 수행하지 않는다.
- ISM의 레지스트리 379행, COM/파일 연결, CustomAction과 선행 런타임 설치는 자동 변환하지 않는다.
- EgViewer의 530개 AppData 대상 파일은 `UserDataSeeds/Roaming/Baron/EG-BIM/Tools/EgViewer`에 보존한다. 실제 AppData 복사와 앱 경로 변경은 미구현이다.
- 패키지의 파일/해시 검증 통과와 EG-BIM의 실행·도면·플러그인·라이선스 호환성은 별개다.
## 설치 오류 0x800B010A 후속 조치
사용자가 연 원본 릴리스는 `unsignedDevelopment`이며 AppxSignature.p7x가 없었다. 설치 가능한 서명본과 패키징 검증용 원본을 구분하도록 안내를 보완했다.
- [개발용 서명 스크립트](../../src/scripts/Sign-DevelopmentMsix.ps1)를 추가했다. 현재 제작기의 `release.json`과 패키지 해시를 확인하고 별도 복사본에 SignTool로 서명한다. `Build-EgbimMsixSample.ps1 -SignDevelopment`로 연결할 수 있다. GUI/공통 Rust 엔진의 운영 서명 기능은 이번 변경 범위가 아니다.
- 실제 서명본: `D:\networkSetup\output\samples\egbim-msix-20261022\signed-test\package.msix`.
- Subject: `CN=Baron MSIX Sample`, 지문 `E0444310862C4BF5802FD36AB4AE2DD3F7D12F05`, 만료 2027-01-22 15:50:26 KST.
- 개인 키는 `CurrentUser\My`의 내보낼 수 없는 키다. `development-signing.cer`에는 공개 인증서만 포함한다. Microsoft/상용 발급 인증서가 아닌 자체 서명 테스트 인증서다.
- SignTool 서명 성공, CMS 서명 검사 성공. 서명본 SHA-256: `a22286f234d0a3bbc7c4a121865154c251fc31c1a684aae3f5ee5eb91d5ca93a`.
- 별도 ZIP 스트림 SHA-256 검사에서 입력 4,703개 파일 및 manifest/blockmap 총 4,705개 항목이 원본과 동일했다. 원본 패키지 해시도 유지됐다. 추가 항목은 AppxSignature.p7x와 AppxMetadata/CodeIntegrity.cat이며 content type 변경을 제외한 기존 항목은 변경되지 않았다. 두 PowerShell 스크립트의 구문 검사도 통과했다.
- SignTool verify는 인증서 미신뢰로 실패했다. 현재 도구 세션이 관리자 권한이 아니어서 컴퓨터 신뢰 저장소는 변경하지 않았다. 서명본 폴더 README에 관리자 PowerShell 등록 명령과 재검증 명령을 제공했다.
- 설치·앱 실행 시험은 미수행이며 `deploymentReady=false`를 유지한다. 원본 릴리스와 기존 호환성 제약도 유지한다.
근거: [Microsoft — 테스트 인증서 생성 및 Trusted People 등록](https://learn.microsoft.com/en-us/windows/msix/package/create-certificate-package-signing), [Microsoft — SignTool MSIX 서명](https://learn.microsoft.com/en-us/windows/msix/package/sign-app-package-using-signtool).
### 동일 오류 재문의: 이 PC의 인증서 신뢰 등록 완료
재문의 시 확인 결과 위 인증서는 `CurrentUser\My`에만 존재했고, `LocalMachine\TrustedPeople`에는 없었다. 공개 CER와 고정 지문·Subject·유효기간을 검사하는 `signed-test/Register-TestCertificate.ps1`을 준비하여 관리자 권한 프로세스로 실행했다. 해당 인증서 하나를 `LocalMachine\TrustedPeople`에 등록했으며 루트 저장소는 변경하지 않았다.
등록 후 Windows SDK SignTool `verify /pa /v`가 종료 코드 0으로 통과했다(검증 1개, 경고 0개, 오류 0개). 등록 결과는 `certificate-registration.json`, 최신 검증은 `trust-verification.json`과 `verify-after-trust.log`에 기록했다. 기존 `signing-result.json`과 `verify.log`는 등록 전 기록으로 보존한다. 설치·앱 실행 시험과 운영 배포 승인은 별개이며 아직 수행하지 않았다.
@@ -0,0 +1,75 @@
# MSIX 레지스트리 설정 편집 및 빌드 반영
현재 MSIX Studio에 **레지스트리** 탭을 추가했다. 항목 추가·수정·삭제를 프로젝트 JSON에 저장하며, CLI와 GUI가 사용하는 공통 Rust 빌드 엔진에서 실제 `registry.dat`를 생성한다. 설정을 입력만 받고 빌드에서 무시하는 기능이 아니다.
## 사용 방법
1. 새로 빌드한 `src/target/debug/msix-studio.exe`를 실행하고 프로젝트를 연다.
2. **레지스트리 → 레지스트리 값 추가**를 누른다.
3. 키 경로, 값 이름, 32/64비트 뷰, 값 형식, 데이터를 입력한다. 값 이름을 비우면 해당 키의 기본값이다.
4. 저장한 뒤 **검증 · 빌드 → 입력 검사 → 빌드 계획 생성 → MSIX 빌드**를 실행한다.
5. 릴리스의 `registry-report.json`에서 항목 수, 패키지 내부 경로와 검증 결과를 확인한다.
현재 직접 빌드한 실행 파일에는 새 UI가 포함된다. 이미 실행 중이던 제작기는 종료 후 새 실행 파일로 다시 열어야 한다. GUI 클릭 시각 검사는 자동 수행하지 않았으며, 프런트엔드 빌드와 편집기 렌더링 테스트를 수행했다.
## 지원하는 설정
앱 전용 `HKLM\Software\회사\제품` 설정을 지원한다. `registry.dat`는 호스트 레지스트리에 가져오지 않으며 패키지 앱이 보는 레지스트리 뷰에 포함된다. 외부 앱이나 시스템 전체에 적용할 설정을 등록하는 방식이 아니다.
| 형식 | 데이터 입력 |
| --- | --- |
| `REG_SZ` | 문자열 그대로, 한글과 빈 문자열 지원 |
| `REG_EXPAND_SZ` | `%LOCALAPPDATA%` 등의 환경 변수 표기를 보존; 제작 PC에서 확장하지 않음 |
| `REG_DWORD` | 0~4294967295, 10진수 또는 `0x` 16진수 |
| `REG_QWORD` | 0~18446744073709551615, JSON 문자열로 저장하여 JavaScript 정밀도 손실 방지 |
| `REG_MULTI_SZ` | 한 줄에 한 문자열, 빈 줄 거부; 전체 빈 데이터는 빈 목록 |
| `REG_BINARY` | `00 FF 12` 또는 `00FF12`; 빈 데이터 지원 |
64비트 뷰는 hive 안의 `REGISTRY\MACHINE\SOFTWARE\...`, 32비트 뷰는 `REGISTRY\MACHINE\SOFTWARE\WOW6432Node\...`로 저장한다. 키 입력에서 WOW6432Node를 중복 지정할 수 없다. 이 뷰 선택은 앱 고유 Software 키용이며 Windows 공유 키·특수 리디렉션 규칙 전체를 자동 분석하지 않는다.
항목 ID, 키와 이름의 중복, 형식별 데이터, 숫자 범위, NUL, 잘못된 키 경로를 검사한다. 키와 값 이름의 중복은 대소문자를 구분하지 않는다. 항목은 10,000개, 입력 데이터는 값당 UTF-8 1 MiB까지다.
## 프로젝트 계약
v2 프로젝트에 선택 필드 `registry`를 추가했다. 기존 파일에 필드가 없으면 빈 목록으로 연다. 프로젝트 저장·백업·계획 revision 검사는 기존 흐름을 그대로 사용하므로 설정 변경 후에는 새 빌드 계획이 필요하다. JSON Schema도 갱신했다.
```json
{
"registry": [
{
"id": "baron-language",
"hive": "HKLM",
"key": "Software\\Baron\\EG-BIM",
"name": "Language",
"view": "64",
"valueType": "REG_SZ",
"data": "ko-KR"
}
]
}
```
위 내용은 프로젝트에 넣는 부분 예시다. 문자열은 literal이며 `[INSTALLDIR]`, `[APPDIR]` 같은 MSI 속성을 자동 치환하지 않는다. MSIX 설치 폴더는 버전별로 달라질 수 있으므로 기존 ISM의 경로 문자열을 그대로 가져오면 안 된다.
## 구현과 검증
- 도메인: `package-domain/src/registry.rs`에서 설정·검증·바이트 인코딩을 담당한다.
- 빌드: 시스템 디렉터리의 `offreg.dll`을 제한된 DLL 검색 방식으로 로드한다. Microsoft Offline Registry API로 메모리 hive를 생성하고 저장한다. 관리자 권한이나 호스트 레지스트리 마운트는 사용하지 않는다. DLL이 없으면 명시적으로 실패한다.
- 검증: 저장한 hive를 다시 열어 값 형식과 바이트를 비교하고, MakeAppx 역추출 후 hive 해시와 값들을 다시 확인한다.
- `registry.dat`, `User.dat`, `User.Classes.dat`를 파일 입력으로 덮어쓰는 경로는 거부한다.
- 보고서: `registry-report.json`을 작업·릴리스·내보내기에 포함한다. 이전 릴리스에 보고서가 없어도 내보내기는 가능하다. 빈 설정은 `notRequested`, 반영 후 비교 통과는 `verified`다.
- 앱에서 수정한 사용자별 값의 업데이트 병합이나 제거 정책은 이번 기능에서 별도 보장하지 않는다.
## 실제 시험 결과
- Rust workspace 단위/통합 테스트: 통과. 기존 프로젝트 호환, 저장·삭제, QWORD 정밀도, Unicode, 중복·경로·예약 파일 차단, 오프라인 hive 생성·읽기·불일치·취소 검사 포함.
- 프런트엔드 TypeScript/Vite 빌드 및 6개 테스트: 통과. 마크업 이스케이프와 삭제 후 행 주소를 확인했다.
- Windows SDK MakeAppx 실제 통합 테스트: 두 제품의 패키징·역추출·레지스트리 검증·보고서 내보내기 통과.
- 실제 설치/실행 시험: 독립 테스트 identity `Baron.RegistryFeatureProbe20261022`를 현 테스트 인증서로 서명하여 설치했다. 패키지 ID가 있는 프로세스에서 Win32 Registry API로 7개 값을 읽었고, 한글 기본값·32비트 뷰·6가지 형식 모두 기대한 타입/바이트와 일치했다. 이후 해당 테스트 패키지만 제거했다.
- 테스트 일반 레지스트리 키는 시험 전후 모두 없었다. 새 인증서를 추가하거나 기존 EG-BIM 앱을 설치·제거하지 않았다.
- 실행 시험 원본: `src/fixtures/registry-probe.rs`. 로컬 증빙: `output/samples/registry-feature-test/runtime-result.txt`, `cleanup-result.json`, 릴리스 `registry-report.json`.
- EG-BIM ISM의 379개 항목 자동 가져오기·변환 및 EG-BIM 재패키징은 수행하지 않았다. 기존 EG-BIM 서명 샘플에는 이번 설정이 소급 적용되지 않는다.
HKCU/User.dat, HKCR 별칭, COM/파일 연결 매니페스트, 레지스트리 삭제 작업, 빈 키만 생성, 조건부 설정, `.reg`/ISM 가져오기는 후속 범위다. 현재 단계에서 지원하지 않는 hive와 값 형식은 빌드 전에 거부한다.
근거: [Microsoft MSIX 레지스트리 동작](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes), [Offline Registry API](https://learn.microsoft.com/en-us/windows/win32/devnotes/offline-registry-library-functions), [ORSetValue](https://learn.microsoft.com/en-us/windows/win32/devnotes/orsetvalue), [ORSaveHive](https://learn.microsoft.com/en-us/windows/win32/devnotes/orsavehive).
@@ -0,0 +1,53 @@
# 개발자 서명 모드
2026-10-23. Rust 공통 엔진과 Tauri 서명 탭에 테스트용 자체 서명 모드를 추가했다. 여기서 개발자 서명은 유료 Microsoft 서명 서비스가 아니라 현재 사용자 저장소의 자체 서명 인증서를 사용하는 기능이다. Windows 개발자 모드와는 별개다.
## 사용
1. 패키지 설정에서 Publisher를 확정한다. 예: `CN=Baron MSIX Sample`.
2. 서명 탭에서 **개발자 서명 · 테스트용 자체 서명**을 선택한다.
3. 테스트 인증서를 생성하거나 목록에서 기존 인증서를 선택한다. 생성과 선택 결과는 로컬 설정에 저장한다.
4. 검증·빌드 탭에서 Windows SDK MakeAppx.exe를 연결하고 새 계획을 생성한 뒤 빌드한다. 같은 폴더의 SignTool.exe를 사용한다.
5. 결과의 공개 CER를 확인하고 설치할 테스트 PC에서 `Install-TestCertificate.ps1`을 관리자 권한으로 실행한다. 등록 대상은 그 인증서 하나이며 `LocalMachine\TrustedPeople`에 추가한다. 제작기는 등록이나 앱 설치를 자동 실행하지 않는다.
모드 선택 자체가 테스트용 서명 활성화다. 별도 Windows 개발자 모드나 전역 테스트 모드 토글은 요구하지 않는다. 이후 빌드에는 선택한 인증서를 재사용한다. 새 인증서는 3개월 유효하며 개인 키는 내보낼 수 없도록 생성한다. 인증서가 만료되거나 Publisher가 바뀌면 선택을 다시 해야 한다. 새 인증서를 선택하면 테스트 PC의 신뢰 등록도 별도로 필요하다.
## 모델과 실행
- 프로젝트: `signing.mode = unsigned | development`; 필드가 없는 기존 프로젝트는 unsigned.
- 로컬: `developerCertificateThumbprint`; 개인 키와 PFX 비밀번호를 프로젝트에 저장하지 않는다.
- 계획: 인증서 지문·DER SHA-256·만료일과 SignTool 경로·SHA-256을 고정한다. 빌드 직전에 다시 검사한다.
- 인증서: 현재 사용자 My 저장소, 개인 키 보유, Subject/Issuer 일치, 정확한 Publisher 일치, 코드 서명 EKU, 유효 기간을 검사한다.
- 빌드: MakeAppx → SignTool SHA-256 패키지 서명 → CMS 서명과 서명자 검사 → WinVerifyTrust → 역추출 및 기존 파일·identity·registry.dat 검증.
- WinVerifyTrust 성공 또는 자체 서명 루트 미신뢰 오류 `0x800B0109`만 허용한다. 후자는 `notTrusted`로 보고한다. 다른 검증 오류는 실패 처리한다.
- 결과: `selfSignedDevelopment`, `signingReport`와 공개 CER·등록 스크립트·서명 로그. 내보내기에도 포함한다. 설치 시험은 `notRun`, 일반 배포 준비는 false로 유지한다.
- 기존 미서명 모드와 운영 서명 프로필 미지원 정책은 유지한다. 내부 EXE/DLL 개별 서명, 타임스탬프와 자동 인증서 갱신은 범위 밖이다.
CLI 예시:
```powershell
package-builder.exe certificate --project D:\packages\demo\package-project.json --create
package-builder.exe certificate --project D:\packages\demo\package-project.json
package-builder.exe certificate --project D:\packages\demo\package-project.json --thumbprint <40자리 지문>
```
CLI 사용 전 프로젝트의 signing.mode를 development로 저장한다. 인증서를 선택하지 않았거나 사용할 수 없는 경우 오류를 표시하며 미서명으로 자동 전환하지 않는다.
## 확인 결과
- Rust 테스트 22개 통과. 별도 Windows SDK 통합 시험은 기존 두 제품의 미서명 빌드·내용 검사·오래된 계획 거부를 확인한다.
- 프런트엔드 테스트 8개 통과; TypeScript/Vite 빌드 통과.
- 작은 레지스트리 샘플을 이용해 기존 신뢰 인증서와 새 미신뢰 인증서로 각각 실제 서명 MSIX 생성 및 역추출 검증 성공.
- 기존 인증서: `trustVerification=passed`, `winVerifyTrust=0x00000000`.
- 새 인증서: `trustVerification=notTrusted`, `winVerifyTrust=0x800B0109`, CMS 서명 검증 성공.
- Publisher 불일치 인증서 선택 거부, 실패 시 기존 선택 보존, 인증서 재조회, 계획의 인증서 해시 변조 거부, 공개 CER·보고서 내보내기 확인.
- 이 시험에서 머신 신뢰 저장소를 변경하거나 앱을 설치하지 않았다. 새 테스트 인증서는 해당 사용자 My 저장소에 남겨 재사용할 수 있다. GUI 화면의 실제 클릭 시험은 수행하지 않았다.
- EG-BIM 대용량 샘플은 다시 생성하지 않았다.
증빙: [시험 결과 JSON](../../src/tests/verification-2026-10-23-developer-signing.json). 샘플 위치: `output/samples/developer-signing-test-20261023`.
## 근거
- [Microsoft: 패키지 서명 인증서 만들기](https://learn.microsoft.com/en-us/windows/msix/package/create-certificate-package-signing)
- [Microsoft: SignTool로 앱 패키지 서명](https://learn.microsoft.com/en-us/windows/msix/package/sign-app-package-using-signtool)
- [Microsoft: WinVerifyTrust](https://learn.microsoft.com/en-us/windows/win32/api/wintrust/nf-wintrust-winverifytrust)
+13 -4
View File
@@ -1,5 +1,9 @@
# Rust + Tauri 구조와 모듈
2026-10-19 실행 범위 변경: 첫 구현은 MsixBackend만 제공하며 WixBackend/Burn 연결은 후속이다. WiX 도구를 설치하지 않은 환경에서도 MSIX 제작이 가능해야 한다. [현재 설계](12-msix-first-builder.md) 참조.
공통 엔진은 제품과 출력 형식에 독립적이다. EG-BIM·Baron·Grimi.exe를 코드에 고정하지 않는다. [범용 출력 계약](11-generic-exe-msix-architecture.md)에 따라 EXE와 MSIX 백엔드를 모두 제공한다.
## 실행 구조
```mermaid
@@ -9,7 +13,9 @@ flowchart TD
CLI[CI용 CLI] --> APP
APP --> DOMAIN[프로젝트 검증 · 파일 해석 · 버전/패치 정책]
APP --> ADAPTER[도구와 저장소 어댑터]
ADAPTER --> WIX[WiX · Burn]
ADAPTER --> BACKEND[PackageBackend · 출력별 capability 검사]
BACKEND --> WIX[EXE: WiX · Burn]
BACKEND --> MSIX[MSIX: Manifest · MakeAppx]
ADAPTER --> SIGN[SignTool · 서명 공급자]
ADAPTER --> STORE[작업 디렉터리 · 불변 릴리스 저장소]
APP --> REPORT[진행 이벤트 · 검증 보고서]
@@ -32,7 +38,8 @@ crates/
package-domain/ # 타입과 순수 검증/비교 규칙
package-engine/ # 작업 흐름, 취소, 상태, 포트 정의
package-infra/ # 파일 I/O, WiX, 서명, Windows 프로세스
templates/wix/ # 버전 관리되는 설치 정의 템플릿
templates/wix/ # EXE/MSI 설치 정의 템플릿
templates/msix/ # AppxManifest·자산 매핑 템플릿
schemas/ # 프로젝트/매니페스트/IPC JSON Schema
tests/fixtures/ # 작은 가상 제품과 기준 MSI 정의
docs/
@@ -51,6 +58,8 @@ docs/
| InstallFlowCompiler | 단계 의존성과 설치 모드 검증, MSI/Burn 실행 정의로 변환 |
| InstallerUiCompiler | UI 템플릿·리소스·바인딩 검증, 백엔드 기능 지원 검사 |
| BuildCoordinator | 작업 잠금, 단계 실행, 체크포인트, 취소, 실패 기록 |
| PackageBackend | format별 capabilities/validate/plan/build/inspect; 제품별 상수 없는 출력 계약 |
| MsixBackend | manifest·자산·의존성 검사, MakeAppx 실행, 패키지 identity 추출 |
| WixBackend | 설치 정의 생성, MSI/MSP/Burn 빌드, 결과 메타데이터 추출 |
| Signer | 서명 정책 적용, 타임스탬프, 실제 서명 검증 결과 반환 |
| ArtifactRepository | 기준 릴리스 조회, 콘텐츠 검증, 불변 결과 등록 |
@@ -58,7 +67,7 @@ docs/
포트는 외부 도구를 가짜 구현으로 바꿀 수 있게 한다. 실제 Rust trait의 비동기 형태는 구현 시 확정하되, 모든 작업은 `BuildContext`, 취소 토큰, 타입이 지정된 요청과 결과를 받는다. 도구 종료 코드만 성공 기준으로 삼지 않는다.
후보 라이브러리는 `serde/serde_json`, `uuid`, `sha2`, `quick-xml`, `tokio`, `clap`, `tracing`이다. 도입 시 유지보수 상태·라이선스·Windows 지원을 확인하고 버전을 고정한다. WiX CLI와 확장, Windows SDK SignTool도 정확한 버전·파일 해시를 `toolchain.lock.json`에 기록한다. WiX 세대가 다른 명령 구문을 혼용하지 않는다.
후보 라이브러리는 `serde/serde_json`, `uuid`, `sha2`, `quick-xml`, `tokio`, `clap`, `tracing`이다. 도입 시 유지보수 상태·라이선스·Windows 지원을 확인하고 버전을 고정한다. WiX CLI와 확장, Windows SDK MakeAppx·SignTool도 정확한 버전·파일 해시를 `toolchain.lock.json`에 기록한다. WiX 세대가 다른 명령 구문을 혼용하지 않는다.
## 작업 수명과 장애 복구
@@ -70,7 +79,7 @@ GUI 종료 시 실행 중 작업을 안내하고 취소를 요청할 수 있다.
## 권한 경계
생성기는 일반 사용자 권한으로 동작한다. 대상 HKLM 레지스트리를 편집하는 것은 생성된 설치기의 역할이다. 빌드 중 사용자 PC에 MSI/MSP를 설치하지 않는다.
생성기는 일반 사용자 권한으로 동작한다. 대상 HKLM 레지스트리를 편집하는 것은 생성된 설치기의 역할이다. 빌드 중 사용자 PC에 MSI/MSP/MSIX를 설치하지 않는다. 설치 검증은 별도 VM에서 수행한다.
웹 UI에는 임의 셸 실행·임의 파일 쓰기 API를 노출하지 않는다. Rust가 허용한 프로젝트와 소스 루트 안에서 요청을 검증한다. 외부 도구는 절대 경로와 인자 배열로 실행하며 `.bat`, `cmd /c`, PowerShell 문자열 조합을 허용하지 않는다. Windows Job Object로 자식 프로세스 수명을 관리하고 실패·취소 시 남은 프로세스를 정리한다. [Rust Command](https://doc.rust-lang.org/std/process/struct.Command.html)
+19 -11
View File
@@ -1,21 +1,29 @@
# 프로젝트와 데이터 모델
2026-10-19 실행 범위 변경: JSON 스키마 버전은 v2를 유지한다. 첫 구현은 formats.msix와 direct 대상만 활성화하고 formats.exe는 생략 가능하다. v1 EXE 가져오기·Store 출력은 후속이며 미지원 입력을 명시적으로 거부한다. 현재 예제는 [msix.package-project.json](examples/msix.package-project.json)이다. [현재 설계](12-msix-first-builder.md) 참조.
**현재 계약은 v2다.** [범용 출력 계약](11-generic-exe-msix-architecture.md)의 `formats`와 `targets`를 사용한다. 아래 MSI 컴포넌트·RegistryEntry·PatchRecord는 EXE 백엔드 전용 타입이며 MSIX 프로젝트의 필수 필드가 아니다.
[범용 v2 예제](examples/generic.package-project.json)는 두 형식을 요청하는 설계 초안이다. null인 UpgradeCode·Publisher·서명 참조, 실제 payload·로고·런타임을 확정하기 전 빌드할 수 없다. 최소 OS와 템플릿 이름도 예시이며 구현된 JSON Schema는 아직 없다. 아래 EG-BIM 예제는 이전 v1 입력으로 보존하고 백업 후 명시적으로 가져온다.
## 설정과 결과를 분리하는 파일 구조
2026-10-26 확정: 기본 확장자는 `.isproj`이며 내용은 UTF-8 JSON v2를 유지한다. 열기·저장·기존 JSON 거부와 로컬 파일 이름은 [프로젝트 파일 형식](13-isproj-project-format.md)을 따른다. 기본 파일명과 저장소에 적용했다.
```text
project/
package-project.json # 사람이 편집하는 제품/패키지 설정
package-project.local.json # PC별 소스 경로, 도구/서명 프로필 참조
EG-BIM.isproj # 사람이 편집하는 제품/패키지 설정 (JSON v2)
EG-BIM.isproj.local.json # PC별 소스 경로, 도구/서명 프로필 참조
identity.lock.json # 제품 계열 및 컴포넌트 식별자 이력
toolchain.lock.json # 승인한 도구/템플릿 버전
.work/jobs/<jobId>/ # 스냅샷, 스테이징, 생성 WXS, 로그
.work/jobs/<jobId>/ # 스냅샷, 대상별 스테이징, WXS/manifest, 로그
releases/<releaseId>/
release.json # 확정된 메타데이터
input.snapshot.json # 설정과 도구, 소스 정보
files.resolved.json # 실제 파일 목록, 원본/서명 후 해시
identity.snapshot.json
artifacts/ # 배포 가능한 파일
baseline/ # MSI, wixpdb, signed payload, 설치 정의
baseline/ # 대상별 입력·서명 payload; EXE는 MSI/wixpdb/설치 정의, MSIX는 manifest/identity
reports/ # 정적 검사와 빌드 당시 검증
patches/<patchId>/ # 기준→목표 패치와 동일 종류의 기록
attestations/<artifactDigest>/ # 이후 VM 시험 결과; 아티팩트는 수정 안 함
@@ -27,14 +35,14 @@ project/
| 타입 | 핵심 필드와 규칙 |
| --- | --- |
| PackageProject | schemaVersion, projectId, product, sources, files, registry, outputs, prerequisites, signing, patchPolicy, installationFlow, installerUi |
| PackageProject v2 | schemaVersion, product, sources, files, formats, targets; 형식별 입력 분리. 별도 프로젝트 ID 없음 |
| InstallationFlow / InstallerUi | 단계 의존성·모드·조건, 테마/페이지/바인딩. [상세 계약](09-install-flow-and-ui.md) 참조 |
| ProductSpec | productId, displayName, manufacturer, architecture, language, installScope, version, engineVersion, upgradeCode, installRoot |
| FileRule | 고유 id, source alias, include/exclude 패턴, destinationDirectory, required |
| ResolvedFile | source-relative path, target-relative path, 크기, SHA-256, PE 정보, componentId/Guid, signingPolicy |
| ProductSpec | displayName, manufacturer, displayVersion; 제품별 선택 메타데이터. 설치 범위·GUID는 formats.exe, MSIX identity는 formats.msix |
| FileRule | 고유 id, source alias, include/exclude 패턴, targetRelativePath, required; v1 destinationDirectory는 형식별 루트와 상대 경로로 변환 |
| ResolvedFile | source-relative path, target-relative path, 크기, SHA-256, PE 정보, signingPolicy; EXE 전용 확장에 componentId/Guid |
| RegistryEntry | id, hive, view, key, name, typed value, component, 설치/제거 정책 |
| IdentityLock | 제품 계열, ProductCode, 컴포넌트 GUID/키 경로, 삭제된 식별자 tombstone |
| ReleaseRecord | releaseId, buildId, 설정 해시, 소스 커밋(선택), 실제 MSI/Burn 식별자, artifacts, 호환성, 서명/도구 정보 |
| IdentityLock | 형식별 계열. EXE: ProductCode·컴포넌트 GUID·tombstone; MSIX: Name·Publisher·Architecture·버전 이력 |
| ReleaseRecord | releaseId, buildId, 설정 해시, 소스 커밋(선택), targets별 실제 MSI/Burn 또는 MSIX identity, artifacts, 호환성, 서명/도구 정보 |
| PatchRecord | patchId/PatchCode, 기준 상태, 목표 releaseId, family/sequence, 필요한 원본·패치, 전체 대체 경로 |
| Artifact | artifactId, kind, 상대 경로, u64 크기, 최종 SHA-256, dependsOn, 서명 결과 |
| ValidationReport | reportId, 대상 해시, 검사 항목, pass/fail/notRun, 환경, 도구 버전 |
@@ -49,7 +57,7 @@ project/
버전·GUID 문자열 형식, 참조 id 존재 여부, 중복 대상 경로, 필수 파일 누락, hive와 설치 범위의 모순은 Rust에서 검사한다. 출력 경로는 소스 루트 밖이어야 한다. 검증 결과는 `severity`, `code`, `fieldPath`, `message`, `remediation`으로 반환하여 화면에서 해당 설정으로 이동하게 한다.
`release.json`은 고정된 프로젝트 설정을 복사하는 것으로 끝내지 않는다. 빌드된 MSI·MSP·EXE에서 읽은 실제 제품 코드·버전과 파일 정보를 기록하고 기대값과 대조한다. 메타데이터 자신의 해시를 자기 안에 넣지 않는다. 메타데이터 서명을 사용할 경우 별도 서명 파일을 생성한다.
`release.json`은 고정된 프로젝트 설정을 복사하는 것으로 끝내지 않는다. 빌드된 MSI·MSP·EXE·MSIX에서 읽은 실제 제품 코드·버전과 파일 정보를 기록하고 기대값과 대조한다. 메타데이터 자신의 해시를 자기 안에 넣지 않는다. 메타데이터 서명을 사용할 경우 별도 서명 파일을 생성한다.
## 편집·빌드 동시성
@@ -1,5 +1,9 @@
# 파일 목록, 설치 위치와 레지스트리
2026-10-19 실행 범위 변경: 공통 파일 해석만 1차에 구현한다. 아래 MSI 컴포넌트·레지스트리 컴파일은 후속이다. MSIX는 패키지 상대 경로와 지원 선언을 검증한다. [현재 설계](12-msix-first-builder.md) 참조.
아래 APPDIR·설치 경로·MSI 컴포넌트·레지스트리 작성 규칙은 EXE 백엔드 계약이다. 공통 파일 수집은 MSIX에도 재사용하되 MSIX는 패키지 상대 경로와 지원 manifest 선언으로 매핑한다. 임의 HKLM 쓰기·사용자 지정 설치 폴더는 자동 변환하지 않는다. 패키지 파일과 쓰기 가능한 앱 데이터를 분리한다. [범용 출력 계약](11-generic-exe-msix-architecture.md) 참조.
## 파일 목록 편집과 해석
파일/폴더 추가, 상대 경로 유지, 포함/제외 패턴, 필수 파일 지정, 대상 폴더 변경을 지원한다. 화면에는 최종 배포 경로, 크기, PE 여부, 서명 상태, 직전 릴리스 대비 추가·수정·삭제를 표시한다. 패턴만 저장하되 빌드마다 확장한 실제 목록도 보존한다.
@@ -1,5 +1,9 @@
# 버전과 GUID 관리
2026-10-19 실행 범위 변경: 1차는 MSIX identity와 4부분 버전 이력이다. 아래 MSI GUID·MSP 규칙은 후속 EXE 계약이다. [현재 설계](12-msix-first-builder.md) 참조.
아래 MSI 3부분 버전·GUID·MSP 계열은 EXE 백엔드 계약이다. MSIX는 Name/Publisher/Architecture와 4부분 Version을 별도 관리하고 Store 대상 추가 규칙을 검증한다. 제품 표시 버전에서 두 형식으로의 매핑은 명시적으로 설정한다. [범용 출력 계약](11-generic-exe-msix-architecture.md) 참조.
## 서로 다른 버전의 구분
| 값 | 목적 |
+14 -8
View File
@@ -1,6 +1,12 @@
# 전체 릴리스 생성 파이프라인
## 작업 단계와 상태
2026-10-19 실행 범위 변경: 1차 실행 경로는 MSIX manifest→MakeAppx→서명/검증→불변 결과이며 EXE/CAB/MSP와 Store 프로필은 후속이다. 미서명 개발 결과와 직접 배포 준비 결과를 구분한다. [현재 설계](12-msix-first-builder.md) 참조.
**공통 조정기 + 형식별 빌드**로 구성한다. 아래 단계 표의 WiX·MSI·Burn·CAB·PE 용량 제한은 EXE 경로다. MSIX는 `manifest/자산 생성 → MakeAppx pack → 실제 identity/내용 검사 → 대상별 서명/검증 → 불변 저장`으로 분기한다. MSIX 결과에는 manifest·SDK 정보·패키지·서명/검증 보고서를 보존한다. Store 제출용 결과와 직접 설치 가능한 결과를 구별한다. [범용 출력 계약](11-generic-exe-msix-architecture.md) 참조.
공통 상태는 대상별로 기록한다. 요청한 EXE/MSIX 중 하나라도 실패하면 전체 작업은 `PartialFailed`이며 성공한 결과를 덮어쓰지 않는다. `Finalized`, 직접 배포 준비, `StoreSubmissionReady`, 실제 Store 승인/공개는 다른 판정이다. `DeploymentReady`는 선택한 배포 대상의 설치 증빙을 요구하며 EG-BIM 전환 증빙을 다른 제품에 강제하지 않는다.
## 공통 작업 단계와 상태
```text
Draft → Planned → Running → Built → ArtifactVerified → Finalized
@@ -29,13 +35,13 @@ Finalized + 별도 설치 시험 증빙 → DeploymentReady
디스크 여유는 원본 스테이징, 서명 복사본, 압축 전후 파일, 임시 파일, 기준 버전과 최종 결과를 포함해 계산한다. 정확히 예측할 수 없는 부분은 보수적인 여유량을 적용한다. 수 GB 파일을 메모리에 통째로 적재하지 않고 스트림으로 복사·해시한다.
## 대용량 출력 프로필
## EXE 출력 프로필과 대용량 지원
| 프로필 | 산출물과 정책 |
| --- | --- |
| offline-layout | Setup.exe + EG-BIM.msi + Data*.cab + prerequisites. 인터넷 없이 설치 가능한 전체 폴더 |
| offline-layout | Setup.exe + Product.msi + Data*.cab + prerequisites. 인터넷 없이 설치 가능한 전체 폴더 |
| network | WebSetup.exe + 원격 MSI/CAB/런타임. 모든 payload에 불변 URL·해시·크기 연결 |
| store-singlefile | 필수 요소를 포함한 단일 오프라인 EXE 또는 적합한 MSI. 크기·서명·Store 검증 실패 시 생성 불가 |
| offline-singlefile | 필수 요소를 포함한 단일 오프라인 EXE. 직접 배포에도 사용하며 크기·서명 검증 필수 |
총 설치 용량이 2GB를 넘는 경우 외부 CAB를 분할한다. 기본 압축 전 CAB 예산은 **512MiB 제안값**이며 파일별 크기와 실제 생성 CAB를 검사한다. 이것은 Windows Installer의 고정 제한값이 아니다. WiX MediaTemplate의 미디어 분할 설정과 단일 대형 파일 처리 설정은 서로 다르다. 도구 버전별 지원을 검증하여 매핑한다. [WiX MediaTemplate](https://docs.firegiant.com/wix/schema/wxs/mediatemplate/)
@@ -43,7 +49,7 @@ Finalized + 별도 설치 시험 증빙 → DeploymentReady
서명할 PE 파일은 4GB 이상에서 서명 문제가 있으므로 단일 EXE에 모든 내용을 무조건 포함하지 않는다. 4GB 미만도 선택한 Burn 버전·컨테이너·압축 방식에서 성공한다는 보장은 없어 실제 빌드와 서명 검증으로 판정한다. [Microsoft 대형 파일 서명 문제](https://learn.microsoft.com/en-us/windows/msix/package/signing-known-issues)
Store의 MSI/EXE 제출은 독립 실행 가능한 오프라인 설치 패키지를 요구한다. 네트워크 stub이나 별도 CAB URL 목록이 단일 제출 파일을 대신한다고 가정하지 않는다. `store-singlefile`이 성립하지 않으면 Store 준비 상태를 실패로 보고하고 MSIX/제품 분할 등 별도 경로를 검토한다. [Store 패키지 요건](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msi/app-package-requirements)
Store의 MSI/EXE 제출은 독립 실행 가능한 오프라인 설치 패키지를 요구한다. 네트워크 stub이나 별도 CAB URL 목록이 단일 제출 파일을 대신한다고 가정하지 않는다. `format=exe, delivery=offline-singlefile, distribution=store`가 성립하지 않으면 해당 대상의 Store 준비 상태를 실패로 보고한다. MSIX 출력은 별도 필수 백엔드이며 해당 제품의 적합성 없이 자동 전환하지 않는다. [Store 패키지 요건](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msi/app-package-requirements)
## 서명 정책
@@ -55,14 +61,14 @@ Rust/WiX는 서명 인증서를 발급하지 않는다. `Signer` 어댑터가 Si
인증서 체인·정책·타임스탬프를 검증하고 서명자, 검증 환경, 검증 시각을 기록한다. 일시적인 서명 서비스 오류는 제한적으로 재시도하되 완료된 파일을 재서명하여 해시를 바꾸지 않는다. 토큰·PIN·개인 키·전체 인증 응답은 로그에 남기지 않는다.
## 전체 설치와 Minor Upgrade
## EXE/MSI 전체 설치와 Minor Upgrade
목표 릴리스마다 신규 설치 가능한 전체 MSI를 만든다. 같은 ProductCode를 유지하는 minor upgrade를 기존 설치에 적용하는 것은 단순히 새 MSI를 실행하는 것과 다르므로 별도의 `upgradeRoute`를 제공한다. MVP에서는 해당 기준에 MSP를 적용하는 경로를 우선한다.
EXE/MSI 대상의 목표 릴리스마다 신규 설치 가능한 전체 MSI를 만든다. 같은 ProductCode를 유지하는 minor upgrade를 기존 설치에 적용하는 것은 단순히 새 MSI를 실행하는 것과 다르므로 별도의 `upgradeRoute`를 제공한다. MVP에서는 해당 기준에 MSP를 적용하는 경로를 우선한다.
MSP가 없는 상태에서 전체 MSI를 대체 업데이트로 사용하려면 해당 버전에 맞는 reinstall/업데이트 동작과 Burn 탐지를 검증한다. 미검증 시 `fallbackArtifactId`만 넣어 실행 가능하다고 표시하지 않고 `FullUpgradeRouteUnavailable`로 차단한다. major upgrade 대상과 minor upgrade 대상의 실행 계획을 분리한다.
## 확정·내보내기
동일 releaseId의 출력 폴더가 있으면 덮어쓰지 않는다. 확정 전 실패한 폴더는 격리하고 명시적인 정리 작업으로 제거한다. 기본 보존 대상은 최종 MSI/CAB/EXE, 서명된 실제 payload, wixpdb, 생성 WXS, 잠금/스냅샷, 로그와 검증 보고서다. 지원 중인 패치가 참조하는 기준은 자동 정리에서 제외한다.
동일 releaseId의 출력 폴더가 있으면 덮어쓰지 않는다. 확정 전 실패한 폴더는 격리하고 명시적인 정리 작업으로 제거한다. EXE 기본 보존 대상은 최종 MSI/CAB/EXE, 서명된 실제 payload, wixpdb, 생성 WXS, 잠금/스냅샷, 로그와 검증 보고서다. 지원 중인 패치가 참조하는 기준은 자동 정리에서 제외한다.
배포 내보내기는 artifacts와 의존 관계, 최종 SHA-256, 설치/업그레이드 경로, 지원 OS·런타임, 패치 기준을 매니페스트로 만든다. offline-layout의 전체 설치 대체 경로는 단일 MSI가 아니라 필요한 CAB·런타임까지 포함한 artifact graph를 가리킨다. 업로드 URL에는 releaseId를 포함한다. payload URL 계획은 Burn 생성 전에 고정하고, 그룹 공개는 별도 배포 시스템에서 수행한다.
+4
View File
@@ -1,5 +1,9 @@
# 기준 버전별 패치 생성
2026-10-19 실행 범위 변경: 아래 MSP 파이프라인은 후속 EXE 범위로 이월한다. 1차 MSIX는 전체 상위 버전 패키지와 App Installer 업데이트를 제공한다. [현재 설계](12-msix-first-builder.md) 참조.
이 문서는 EXE/MSI 백엔드의 MSP 생성 계약이다. MSIX 업데이트는 버전 증가 패키지와 Windows 배포 기능을 사용하며 MSP·Burn 기준 상태를 적용하지 않는다. [범용 출력 계약](11-generic-exe-msix-architecture.md) 참조.
## 입력 계약
패치는 이전 릴리스의 폴더와 새 폴더만 비교해 만드는 ZIP이 아니다. 보존한 기준 MSI/빌드 정보와 목표 전체 릴리스를 사용해 WiX MSP를 생성한다. WiX는 기준·업데이트 입력으로 MSI 또는 wixpdb와 관련 payload를 사용한다. 정확한 도구 호출은 버전별 백엔드가 담당한다. [WiX Windows Installer patches](https://docs.firegiant.com/wix/tools/patches/)
+14 -3
View File
@@ -1,7 +1,18 @@
# Tauri 화면과 명령 인터페이스
2026-10-19 실행 범위 변경: 1차 화면은 앱 정보·파일 구성·패키지 설정·서명·업데이트·검증/빌드다. EXE/Store 선택·과정/마법사 편집·MSP는 첫 UI에 노출하지 않는다. 아래 확장 화면/명령은 후속 계약이다. [현재 설계](12-msix-first-builder.md) 참조.
## 형식별 제작 화면과 CLI
공통 제품/파일 설정 다음에 **출력 대상**을 편집한다. 각 대상은 `format`, `delivery`, `distribution`을 별도 선택한다. EXE 탭은 설치 경로·레지스트리·과정/마법사·MSP를, MSIX 탭은 identity·manifest·앱 자산·의존성·capabilities·업데이트를 편집한다. MSIX 설치 경로는 OS 관리로 안내한다. Store 설정은 해당 대상을 선택한 경우에만 요구한다.
CLI의 validate/plan/build/export는 `--target <targetId>` 또는 `--all-targets`를 받도록 확장한다. CLI와 GUI가 같은 capability 검사를 사용한다. 모든 대상 요청 중 하나만 성공하면 전체 성공 코드를 반환하지 않고 대상별 결과와 `PartialFailed`를 반환한다. patch 명령은 EXE/MSI 대상 전용이며 MSIX에는 버전 증가 빌드를 안내한다. [v2 출력 계약](11-generic-exe-msix-architecture.md)이 아래 기존 EXE 중심 화면에 우선한다.
## 화면 구성
프로젝트 생성·열기·저장은 `.isproj`만 지원한다. 시작 화면의 두 버튼과 파일 다이얼로그, 최근 목록, CLI 모두 [프로젝트 파일 형식 계약](13-isproj-project-format.md)을 따른다. 기존 `.json` 프로젝트 호환·변환은 제공하지 않는다.
| 화면 | 입력/행동 | 결과 |
| --- | --- | --- |
| 프로젝트 | 생성·열기·저장, 제품명/제조사/엔진, 소스 alias 연결 | 현재 revision, 미확정 필드 |
@@ -53,8 +64,8 @@
다음은 구현 예정인 명령 형식이다. 현재 실행 가능한 명령이 아니다.
```powershell
package-builder validate --project .\package-project.json --format json
package-builder plan release --project .\package-project.json --profile offline-layout
package-builder validate --project .\EG-BIM.isproj --format json
package-builder plan release --project .\EG-BIM.isproj --profile offline-layout
package-builder build --plan-id <planId>
package-builder plan patch --target <releaseId> --base-state <baseStateId>
package-builder job status --id <jobId> --format json
@@ -67,4 +78,4 @@ CI용 `release`/`patch` 편의 명령은 같은 엔진에서 plan+build를 연
## 생성 프로그램 자체 배포
Tauri 앱 자체의 설치 패키지와 버전은 EG-BIM의 프로젝트/제품 GUID와 분리한다. 개발 머신의 WebView2, WiX, SDK 및 서명 연동 도구를 검사하는 환경 진단 화면을 제공한다. 필요한 외부 도구를 자동 다운로드하거나 번들에 재배포하는 기능은 라이선스·고정 버전 검토 후 추가하며, 최초 구현은 관리자가 준비한 도구 프로필을 사용한다.
Tauri 앱 자체의 설치 패키지·버전·identity는 각 고객 제품의 EXE/MSIX identity와 분리한다. 개발 머신의 WebView2, WiX, SDK 및 서명 연동 도구를 검사하는 환경 진단 화면을 제공한다. 필요한 외부 도구를 자동 다운로드하거나 번들에 재배포하는 기능은 라이선스·고정 버전 검토 후 추가하며, 최초 구현은 관리자가 준비한 도구 프로필을 사용한다.
+23 -9
View File
@@ -1,15 +1,24 @@
# 검증 계획과 구현 순서
2026-10-19 실행 범위 변경: 현재 출시 게이트는 두 독립 제품의 **MSIX** 생성·서명·설치·업데이트·제거다. 아래 두 형식 통합/EXE/EG-BIM 게이트는 후속으로 이월한다. [현재 설계](12-msix-first-builder.md) 참조.
## 후속 두 형식 통합 게이트
후속 두 형식 완성 시에는 EXE와 MSIX를 모두 검증한다. 첫 MSIX 출시에는 아래 EXE 검증을 요구하지 않는다. 최소 두 독립 가상 제품에서 두 형식의 생성·서명/검증·신규 설치·실행·업데이트·제거와 데이터 보존을 VM으로 검증한다. MSIX는 Publisher/인증서 불일치, 누락 자산·런타임, OS 조건, 임의 설치 경로·미지원 기능, 업데이트 실패를 추가 시험한다. EXE/MSIX 동시 요청의 부분 실패와 대상별 불변 결과도 검사한다. Store 계정과 EG-BIM·자체 서버 없이 이 게이트를 통과할 수 있어야 한다.
제품명·경로·레지스트리·게시자를 서로 다른 값으로 시험해 하드코딩을 탐지한다. EG-BIM 실제 전환·CAD 업무 회귀와 자체 서버 승격은 별도 적용 게이트다. Store 제출 준비 검사와 심사 승인도 분리한다. [상세 계약](11-generic-exe-msix-architecture.md), [BL-46~53](../backlog/README.md) 참조.
## 자동 검증
| 계층 | 검증할 핵심 동작 |
| --- | --- |
| 도메인 | MSI 버전 경계, GUID 수명, component 이동/삭제 판정, 지원 기준 상태 비교 |
| 도메인 | 공통 출력 대상/capability; EXE의 MSI 버전·GUID·기준 상태; MSIX identity·4부분 버전·배포 대상 조건 |
| 프로젝트 계약 | JSON Schema와 Rust 역직렬화 일치, 알 수 없는 필드, 마이그레이션, revision 충돌 |
| 파일 해석 | 포함/제외, 필수 파일 누락, 대소문자 충돌, 경로 탈출/reparse point, 원본 무변경 |
| 레지스트리 | 타입 범위, 32/64비트, XML/MSI 문자열 escaping, 지원하지 않는 HKCU 정책 차단 |
| 작업 엔진 | 가짜 WiX/Signer를 통한 단계 실패, 취소, 중복 요청, 체크포인트, 잠금 충돌 |
| 실제 도구 통합 | 작은 샘플 제품으로 MSI/EXE/MSP 생성, 실제 식별자와 서명/해시 추출 |
| 작업 엔진 | 가짜 WixBackend/MsixBackend/Signer를 통한 단계 실패, 취소, 중복 요청, 체크포인트, 잠금 충돌 |
| 실제 도구 통합 | 작은 독립 제품들로 MSI/EXE/MSP/MSIX 생성, 실제 식별자와 서명/해시 추출 |
| 대용량 | 총 2GB 초과와 여러 CAB, 크기 합산 오버플로, 메모리 사용, 개별 제한 초과 실패 |
| UI/IPC | 계획 이후 설정 변경 차단, 권한 없는 Command, 입력/로그의 HTML 실행 방지 |
| 설치 과정/UI | 의존 단계 삭제·순환·조건 오류 차단, 대화형/무인 결과 일치, patch/repair Feature 유지, CustomAction 복구, WebView2 없는 PC |
@@ -22,7 +31,8 @@ CAB 시험에는 압축이 매우 잘 되는 0바이트 반복 데이터만 쓰
| 시나리오 | 합격 조건 |
| --- | --- |
| 신규 설치 | 기본/사용자 지정 경로, 파일·레지스트리·런타임·실행 정상 |
| EXE 신규 설치 | 기본/사용자 지정 경로, 파일·레지스트리·런타임·실행 정상 |
| MSIX 신규 설치/업데이트 | OS 관리 경로·앱 실행·의존성·서명 신뢰 확인, 버전 증가 업데이트·제거·데이터 보존 |
| 기존 InstallShield 전환 | 실제 배포본별 탐지, 설정·CUI·도면 보존, 기존 삭제 스크립트 영향 확인 |
| 오프라인 전체 설치 | 네트워크 차단 상태에서 CAB와 런타임을 모두 로컬로 해석 |
| 네트워크 설치 | 단절·재시도·캐시·해시 불일치 처리, 불완전 다운로드 미적용 |
@@ -31,28 +41,32 @@ CAB 시험에는 압축이 매우 잘 되는 0바이트 반복 데이터만 쓰
| 파일 버전/수정 | 동일 PE 버전의 변경 파일, 수정된 비버전 파일, 누락 파일의 결과 검증 |
| 실패/재부팅 | 앱 실행 중, 파일 잠김, 디스크 부족, 3010/1641, rollback와 복구 판정 |
| 설치 후 유지보수 | Burn 표시와 MSI 상태, repair, uninstall, 다음 패치·전체 업그레이드 |
| Store 출력 | 단일 파일, 무다운로드, silent 경로, 서명, 버전별 고정 URL 조건 |
| Store EXE 출력 | 단일 파일, 무다운로드, silent 경로, 서명, 버전별 고정 URL 조건 |
| Store MSIX 출력 | 제출 identity/버전/manifest·자산·지원 선언 검증. 제출 파일과 직접 설치용 서명본 구분; 실제 심사는 별도 |
사용자 데이터 보존은 해시가 반드시 동일해야 하는 공급자 파일과 별도 판정한다. 저장된 백업이 있다는 것만으로 복구 성공이라고 하지 않고 실제 복원과 앱 재실행까지 확인한다.
## 구현 순서
아래 표는 영역별 구현 설명이다. 실제 작업 배치는 [통합 백로그](../backlog/README.md)와 [단독 개발 일정](../plan/10-development-schedule.md)을 따른다. 기존 제거 및 Rust/Burn 연결 실험은 초기에 수행하고, 실제 EG-BIM 전환 구현은 GUI 완성보다 앞에 배치한다.
| 단계 | 구현 내용 | 완료 기준 |
| --- | --- | --- |
| 1. 기준 확정 | 실제 배포 payload, ISM 대조, 런타임, 출시본 GUID, 도구 버전 | 출처가 기록된 첫 프로젝트와 전환 위험 목록 |
| 1. 기준 확정 | 두 가상 제품, EXE/MSIX 도구·범위·서명, EG-BIM 자료는 별도 조사 | 범용 빌드 계약과 적용 전환 위험을 각각 기록 |
| 2. Rust 엔진/CLI | 스키마, 파일 해석, identity lock, 검증/계획 | fixture에서 안정적인 목록·GUID와 설명 가능한 오류 |
| 3. 전체 릴리스 | WXS 생성, MSI/Burn, 분할 CAB, 서명 어댑터 | 작은 제품과 2GB 초과 제품의 전체 설치 시험 통과 |
| 4. 버전별 패치 | 기준 저장소, diff, 호환성, MSP, 순서/이력 | 최소 두 기준 상태에서 동일 목표 도달·repair·제거 통과 |
| 5. Tauri UI | 프로젝트/파일/레지스트리/릴리스/패치/작업 화면 | CLI와 같은 계획·결과, 긴 작업 중 UI 응답 유지 |
| 필수 MSIX. 패키지 출력 | manifest·SDK·서명·업데이트·제작 화면 | 독립 제품 두 개의 두 형식 시험과 부분 실패 검사 통과 |
| 6. EG-BIM 전환 | 실제 파일/레지스트리/백업, 기존 업데이터 연계 | 실제 IS 출시본→WiX→후속 MSP 경로 시험 통과 |
| 7. 배포 연결 | 내보내기, 외부 증빙, 업로드 계약, Store 가능성 | 불변 파일과 기준 정보가 배포 시스템에 연결 |
버전별 패치는 필수 범위이며 최초 제품 완성에서 제외하지 않는다. 다만 작은 시험 제품으로 엔진을 검증한 뒤 실제 EG-BIM 데이터를 적용한다. UI 작업은 엔진 계약이 안정화된 후 병행할 수 있다.
EXE/MSI 버전별 MSP와 MSIX 버전별 업데이트는 필수 범위이며 최초 제품 완성에서 제외하지 않는다. 다만 작은 시험 제품으로 엔진을 검증한 뒤 실제 EG-BIM 데이터를 적용한다. UI 작업은 엔진 계약이 안정화된 후 순차 수행한다. 단독 개발자의 병렬 작업 용량을 가정하지 않는다.
설치 과정/화면 편집 요구는 [09 설계](09-install-flow-and-ui.md)를 따른다. 단계 2~3에 과정 검증과 기본 테마 출력을, 단계 5에 과정/UI 편집기를 추가한다. 자유로운 페이지/분기 지원은 Rust/Burn 연결 실험 후 사용자 정의 BA로 구현하며 기본 테마 편집만으로 해당 요구를 완료 처리하지 않는다.
## 정식 배포를 막는 미확정 사항
실제 설치용 소스 폴더, 제조사 표시명, 런타임 목록, 서명 프로필, 기존 설치본 목록과 파괴적 제거 동작, 업데이터의 MSI/MSP 실행 계약, Burn과 패치의 유지보수 동작은 아직 확정되지 않았다. 초안 설정으로 편집·차이 계산·개발 패키지 검증을 진행할 수 있지만 정식 릴리스의 배포 준비 상태는 부여하지 않는다.
각 제품의 실제 소스·표시명·런타임·서명과 선택 형식의 identity를 확정해야 해당 제품의 정식 배포 준비를 판정할 수 있다. EXE는 Burn/MSP 유지보수, MSIX는 manifest·서명·업데이트 증빙을 요구한다. EG-BIM의 기존 설치본·파괴적 제거·NuGet 계약 미확정은 EG-BIM 전환을 차단하며 다른 제품의 빌드를 막지 않는다. 초안 예제만으로 정식 릴리스 준비를 선언하지 않는다.
이 문서 작성 단계에서는 설계 문서의 경로·예제 JSON·문서 간 일관성만 검사한다. Rust 컴파일, WiX 빌드, 실제 서명, 설치·패치·Store 심사는 수행하지 않았다.
이 문서 작성 단계에서는 설계 문서의 경로·예제 JSON·문서 간 일관성만 검사한다. Rust 컴파일, WiX/MakeAppx 빌드, 실제 서명, 설치·패치·Store 심사는 수행하지 않았다.
+10 -2
View File
@@ -1,7 +1,13 @@
# 설치 과정 편집과 설치 UI 커스터마이징
2026-10-19 실행 범위 변경: 이 문서의 EXE 설치 과정·BA·HTML 페이지 편집은 후속 범위다. 첫 제품의 Tauri UI는 MSIX 제작 설정을 편집하며 설치 마법사를 생성하지 않는다. [현재 설계](12-msix-first-builder.md) 참조.
이 문서의 최종 사용자 설치 과정·마법사·Burn BA 커스터마이징은 **EXE 출력 전용**이다. MSIX는 Windows/Store가 설치 UI와 경로를 관리하며 제작기는 manifest의 이름·아이콘·지원 선언을 편집한다. 두 형식의 설정을 혼합하거나 미지원 단계를 조용히 생략하지 않는다. [범용 출력 계약](11-generic-exe-msix-architecture.md) 참조.
## 목표
2026-10-19에 InstallShield와 Inno Setup의 마법사 패턴을 참고해 [설치 과정·UI를 재설계](10-installer-wizard-redesign.md)했다. 아래 실행/데이터 계약을 유지하면서 페이지 흐름, 편집 화면과 실패·취소·재부팅 상태를 구체화한다.
패키지 제작자가 설치 단계를 추가·삭제·비활성화·재배치하고, 최종 사용자가 보는 설치 화면도 변경할 수 있게 한다. 제작자가 정하는 실행 정책과 설치 사용자가 선택하는 옵션은 구분한다.
- **설치 과정 편집기**: 환경 검사, 런타임, 제품 설치, 설정 이전, 바로가기, 완료 후 실행 등 작업을 설정한다.
@@ -93,7 +99,7 @@ UI는 일반 권한을 유지하고 시스템 변경은 Burn/MSI의 적합한
## 데이터 계약과 무인 설치
`PackageProject`에 `installationFlow`와 `installerUi`를 추가한다. 기존 초안 예제에서 생략한 경우 버전이 고정된 기본 과정/테마를 적용하고 빌드 스냅샷에는 전체 정의와 리소스 해시를 저장한다.
v2 `PackageProject.formats.exe`에 `installationFlow`와 `installerUi`를 둔다. 기존 초안 예제에서 생략한 경우 버전이 고정된 기본 과정/테마를 적용하고 빌드 스냅샷에는 전체 정의와 리소스 해시를 저장한다.
| 정의 | 필드 |
| --- | --- |
@@ -110,7 +116,9 @@ Feature 선택은 MSI 모델에 연결하고 repair/patch에서 설치된 선택
## 검증과 구현 순서
단계 카탈로그·검증 → 과정 편집기 → 기본 테마 편집 → Rust/Burn 연결 실험 → 사용자 정의 BA·페이지 편집 순서로 구현한다. 모두 기존 전체 릴리스·MSP와 같은 스냅샷·서명·검증 절차를 적용한다.
최소 Rust/Burn 연결·부트스트랩 실험 → 단계 카탈로그·검증 → 과정 편집기 → 기본 테마 편집 → 사용자 정의 BA·페이지 편집 순서로 진행한다. 모두 기존 전체 릴리스·MSP와 같은 스냅샷·서명·검증 절차를 적용한다.
2026-10-19의 [통합 일정](../plan/10-development-schedule.md)에 따라 위험 실험(BL-08)은 기준 확정 단계에 수행한다. 본 편집기와 사용자 정의 BA 구현은 공통 엔진 및 적용 계약 검증 이후 진행한다.
- 의존 단계 삭제, 순환, 모드/조건 오류의 사전 검출.
- 대화형·숨김·무인 모드에서 관리 파일/레지스트리 결과의 동등성.
@@ -0,0 +1,123 @@
# 설치 마법사와 편집 UI 재설계
2026-10-19 실행 범위 변경: 이 시안은 후속 EXE 출력용 자료로 보존한다. 이번 첫 제품 시안은 MSIX 제작 프로그램 화면이다. [현재 설계](12-msix-first-builder.md) 참조.
아래 EG-BIM 시안은 **범용 제작기의 EXE 출력용 참고 테마**다. 제품명·로고·경로·문구는 프로젝트에서 교체한다. MSIX의 OS 설치 UI를 이 마법사로 대체하지 않으며 MSIX 제작 화면은 별도 manifest/출력 설정을 제공한다. [범용 출력 계약](11-generic-exe-msix-architecture.md) 참조.
변경일: 2026-10-19. InstallShield와 Inno Setup의 마법사 패턴을 참고해 EG-BIM의 설치 과정과 화면을 구체화한다. 패키지 엔진은 기존 WiX MSI/MSP + Burn 설계를 유지한다. 설치기 제품을 바꾸거나 기존 제거 동작을 그대로 복제하는 변경이 아니다.
현재 결과는 상호작용 시안과 구현 설계다. 실제 설치·백업·서명 검사·관리자 승격·재부팅은 실행하지 않으며 표시되는 경로·진행률·검증 결과는 예시다.
## 참고한 패턴
| 출처 | 확인한 특징 | EG-BIM에 적용한 설계 |
| --- | --- | --- |
| [InstallShield: 기본 패키지와 의존 패키지](https://docs.revenera.com/installshield31helplib/helplibrary/StePrimDepend.htm) | 설치된 기본 패키지 상태에 따라 최초 설치와 유지 관리 모드를 구분한다. | 제품 탐지를 먼저 하고 신규 설치·전환·패치·복구·제거를 분리한다. 런타임 존재만으로 제품 설치 여부를 판정하지 않는다. |
| [InstallShield: Wizard Pages 편집](https://www.revenera.com/blog/software-installation/putting-the-wizard-in-wizard-pages/) | 페이지 생성·복제·템플릿으로 화면을 구성하는 편집 패턴을 설명한다. 오래된 버전의 설명이므로 내부 XML 형식은 재사용하지 않는다. | 제작기를 페이지 목록·미리보기·속성 패널로 구성한다. 페이지와 동작 연결은 자체 계약으로 검증한다. |
| [Inno Setup: WizardStyle](https://jrsoftware.org/ishelp/topic_setup_wizardstyle.htm) | classic/modern의 화면 표현과 테마 설정을 제공한다. | 간결한 제목/설명, 본문, 일관된 하단 버튼을 갖는 마법사형을 제공한다. 지원 여부는 선택한 WiX/BA 백엔드에서 별도 판정한다. |
| [Inno Setup: 사용자 정의 페이지](https://jrsoftware.org/ishelp/topic_scriptpages.htm) | 기존 페이지 사이에 사용자 정의 입력 페이지를 삽입하고 입력값을 읽을 수 있다. | 페이지 ID와 조건·입력 바인딩을 가진 페이지 카탈로그를 둔다. 자유 페이지는 사용자 정의 BA에서 처리한다. |
| [Inno Setup: Tasks](https://jrsoftware.org/ishelp/topic_taskssection.htm) | 추가 작업을 체크박스/라디오 버튼으로 표현하고 실제 설치 항목에 연결한다. | 바로가기 같은 사용자 선택 작업을 필수 구성요소와 구분한다. 선택 UI만 있고 실제 작업 연결이 없는 설정을 검증 오류로 처리한다. |
| [Inno Setup: CloseApplications](https://jrsoftware.org/ishelp/topic_setup_closeapplications.htm) | 설치 준비 단계에서 사용 중인 파일과 앱 종료를 다루며 강제 종료 시 작업 손실 위험을 설명한다. | 저장·종료 안내와 재확인 상태를 제공한다. EG-BIM의 미저장 도면을 강제 종료하는 기본 동작은 채택하지 않는다. |
위 자료는 2026-10-19 확인했다. 제품별 외형·동작에서 참고한 패턴이며, 두 도구의 모든 기능이 WiX 기본 테마에 그대로 매핑된다는 의미는 아니다.
## 디자인 방향
두 외형은 동일한 페이지 데이터와 실행 계약을 사용한다. 기본 제안은 **진행 안내형**이다. 기존 설치본 전환의 준비·백업·적용 단계를 쉽게 이해하도록 현재 구간을 왼쪽에 표시한다. **표준 마법사형**은 Inno Setup 계열에서 익숙한 상단 제목/설명·중앙 본문·하단 버튼 구조로 화면을 간결하게 구성한다.
- 제목은 현재 할 일을 설명하고 본문에는 해당 단계에 필요한 입력만 둔다.
- 하단 버튼은 `이전 / 다음 또는 작업 시작 / 취소`, 완료 시 `마침`으로 일관되게 배치한다.
- 단계 목록은 진행 표시이며 클릭으로 필수 단계를 건너뛰는 탐색 메뉴가 아니다.
- 신규 설치의 위치·구성요소·바로가기 선택은 한 화면의 세 구역으로 묶는다. 설정이 많아지면 위치/구성/추가 작업 페이지로 나누되 같은 변수를 사용한다.
- 기존 설치본의 경로와 패치/복구의 Feature 상태는 읽기 전용이다. 제거에서는 제거 대상과 유지할 데이터를 분리해서 표시한다.
- 재부팅 필요, 백업 실패, 설치 실패, 취소는 각각 별도 상태로 표시한다. 실패를 완료 체크 표시로 표현하지 않는다.
- 운영 화면에는 MSI GUID·도구 버전·개발 백엔드 이름을 노출하지 않는다. 진단 정보는 상세 로그에 둔다.
[상호작용 시안 원본](../../output/egbim-installer-wizard.html), [브라우저 미리보기](../../output/egbim-installer-wizard-preview.html). 이전 [초기 시안](../../output/egbim-installer-preview.html)은 비교 자료로 보존한다.
## 상황별 화면 흐름
실제 모드는 탐지 결과와 승인된 적용 경로에서 결정한다. 시안 위의 상황/상태 선택기는 검토용이며 배포되는 설치기의 메뉴가 아니다.
| 모드 | 사용자 화면 | 핵심 제한 |
| --- | --- | --- |
| 신규 설치 | 시작 → 약관 → 위치/구성/바로가기 → 최종 확인 → 준비 → 적용 → 완료/재부팅 | 약관 미동의·경로 오류를 차단. 필수 본체는 해제 불가. |
| 기존 InstallShield 전환 | 기존 제품 탐지 → 최종 확인 → 준비/파일 확보/백업 → 전환/복원 → 완료/재부팅 | 지원 legacy만 허용. 기존 경로 유지. 백업 검증 전에 제거 금지. |
| 후속 MSP | 기준/목표 확인 → 최종 확인 → 준비/백업 → 패치/설정 확인 → 완료/재부팅 | Feature·경로 유지. 기준 상태 불일치 시 검증된 대체 경로 안내 또는 차단. |
| 설치 복구 | 복구 안내 → 최종 확인 → 원본 확보/준비 → 복구 → 완료/재부팅 | 새 WiX 설치 체계의 지원 상태만 대상. 필요한 원본 미확보 시 적용 차단. |
| 제거 | 제거 안내 → 대상/보존 데이터 확인 및 동의 → 준비 → 제거 → 완료/재부팅 | 사용자 도면·설정·백업과 타사 CAD 소유 연결은 유지. 공유 런타임 일괄 제거 금지. |
약관 버전이 달라진 업데이트에는 약관 단계를 추가한다. 시안의 전환·패치 경로는 재동의가 필요 없는 예시이며, 실제 생략 조건은 기존 동의 버전·배포 약관 정책으로 판정한다. 무인 설치는 별도 명시적 동의 입력 계약을 사용한다. 화면을 숨긴 것만으로 동의했다고 간주하지 않는다.
복구·제거 화면의 데이터 보존 정책은 새 설치기에 적용할 설계다. 기존 InstallShield 제거 스크립트에 자동으로 적용된다고 보장하지 않는다. legacy 제거는 전환 핸들러와 검증된 백업 절차를 통해서만 실행한다. 일반적인 구성 변경(Modify)은 Feature 변경 경로가 검증될 때 별도 모드로 확장하고 이번 시안에는 노출하지 않는다.
## 페이지별 계약
| 페이지 ID | 표시/입력 | 다음 단계 조건 | 사용자 버튼 |
| --- | --- | --- | --- |
| welcome | 제품/작업·탐지된 설치 위치·보존 정책 | 지원 가능한 제품/버전/모드, 차단 오류 없음 | 다음, 취소 |
| license | 승인된 약관 전문/버전·동의 | 해당 약관 동의 | 이전, 다음, 취소 |
| options | 설치 위치, 필수/선택 Feature, 바로가기 | 경로/권한/지원 Feature 검증 | 이전, 다음, 취소 |
| review | 실제 실행 계획, 경로, 구성, 백업/보존, 필요한 권한 | 유효한 planId/revision, 제거 시 명시적 확인 | 이전, 설치/업데이트/복구/제거 시작, 취소 |
| prepare | 실행 앱, 패키지 확보·서명·해시·디스크·백업 결과 | 모든 필수 준비 성공, 앱 종료, 적용 직전 허가 유효 | 취소, 차단 원인별 다시 확인 |
| apply | 현재 작업/전체 상태, 엔진이 제공한 진행률, 로그 | 엔진 완료·오류·취소·재부팅 이벤트 | 가능한 지점에서 취소 요청; 이전 없음 |
| finish | 설치 결과, 복원 상태, 백업 위치·실행 선택 | 성공이며 재부팅 불필요 | 마침 |
| rebootRequired | 재부팅 필요 이유, 지금/나중에 선택 | 사용자의 명시적 선택; 기본은 나중에 | 마침 또는 재시작 |
| failure | 실패 단계, 기존 제품 상태, 백업/로그, 다음 조치 | 복구 상태에 따라 다름 | 재검사 또는 진단 안내/닫기 |
| cancelled | 변경 전 취소 여부 또는 적용 후 복구 결과 | 엔진이 확인한 실제 상태 | 닫기 |
`prepare`에는 다운로드·캐시 확보와 로컬 무결성 검사를 포함한다. 온라인 흐름에서는 적용 허가를 재확인하고, 오프라인 흐름은 승인된 배포 정책을 적용한다. `review`의 시작 버튼 이후에는 입력값을 고정하고 작업 ID 하나로 실행한다. 준비 실패 후 입력 수정이 필요하면 계획을 다시 만든다.
## 실패·취소·완료 동작
| 상태 | 표현과 허용 동작 |
| --- | --- |
| 실행 앱 발견 | 저장/종료 안내 → 다시 확인. 자동 강제 종료와 사용자 작업 저장 추정 금지. |
| 다운로드/공간/백업 실패 | 실제 원인을 표시. 제거 전이라면 기존 제품 미변경을 표시하고 안전한 준비 단계만 재시도. |
| 적용 전 취소 | 확인 후 종료. 만들어진 백업/캐시의 보존 정책은 명시하고 기존 제품은 유지. |
| 적용 중 취소 | 취소 요청 접수와 처리 중 상태를 표시. 프로세스를 강제 종료하거나 즉시 성공으로 표시하지 않는다. |
| 적용 실패 또는 취소 후 상태 불명확 | 로그·백업 보존, 복구 진단 안내. 무조건 다시 설치/전체 설치 버튼을 제공하지 않는다. |
| 롤백 완료 확인 | MSI 복구와 외부 런타임/설정 보상 결과를 구분. 각 결과가 확인된 범위만 복구 완료로 표시. |
| 재부팅 필요 | ‘설치 성공·실행 확인 대기’를 구분. 앱 실행 선택 숨김, 지금 재부팅은 기본 미선택. 실제 재부팅은 명시적 동의 후 수행. |
| 정상 완료 | 호환 설정 복원과 실제 확인 항목 표시. 앱 실행은 대화형·원래 사용자 권한. 백업 자동 삭제 금지. |
시안의 ‘작업 진행’ 버튼은 자동 설치 이벤트를 수동으로 넘겨보는 검토용 조작이다. 실제 UI의 prepare/apply에는 이 버튼이 없으며 Burn/작업 서비스 이벤트로 진행한다. ‘다시 확인’과 ‘다시 검사’도 시안에서는 정상 상태로 바꾸는 모의 동작이며 실제 파일/프로세스를 검사하지 않는다.
## 제작기의 설치 과정·UI 편집 화면
화면 편집은 **왼쪽 페이지 목록 / 중앙 미리보기 / 오른쪽 속성**의 세 영역으로 구성한다. 설치 과정 탭에서는 같은 공간을 **작업 목록 / 모드별 실행 순서 / 조건·입력·복구 정책**으로 바꾼다. 최종 사용자 마법사의 단계 이동과 제작자의 페이지 편집은 구분한다.
| 영역 | 편집 동작 | 제약과 피드백 |
| --- | --- | --- |
| 상단 설정 | install/upgrade/patch/repair/uninstall 미리보기, 기본 테마/사용자 정의 BA 선택 | 선택 백엔드가 지원하지 않는 기능 표시. 모드 변경은 화면 미리보기이며 시스템 실행 아님. |
| 페이지 목록 | 템플릿에서 추가, 선택, 복제, 허용 순서 변경, 표시 조건 | review/prepare/apply/결과의 실행 계약은 잠금. 페이지 숨김으로 필수 작업을 삭제하지 않음. |
| 중앙 미리보기 | 선택 페이지와 이전/다음 연결, 정상/차단/실패/재부팅 상태 | 실제 설치 미수행. native 출력 시험 필요 표시. |
| 속성 패널 | 제목/설명/번역 키, 로고/이미지, 지원 색상/글꼴, 레이아웃 슬롯, 입력 변수와 검증 | 읽기 전용 설치 경로/필수 Feature 등 모드 제약을 즉시 표시. 임의 JS/셸 바인딩 불가. |
| 설치 과정 | 카탈로그 추가, 선택 단계 활성화, 의존성 범위 내 순서, 실패 정책 확인 | 필수 백업/무결성/설치 작업 제거 차단. trusted catalog의 권한/복구 정책은 임의 약화 불가. |
| 검증 결과 | 페이지·단계·변수 경로와 오류 원인/수정 위치 | 미지원 기능·순환·필수 입력 누락이면 배포 빌드 불가. |
설치 과정 편집기 자체의 세 영역 UI는 이 문서에서 구체화했으며 이번 상호작용 파일은 **배포되는 설치 마법사**의 화면을 구현한 시안이다. 제작기 편집 화면의 구현 완료를 의미하지 않는다.
## 커스터마이징 데이터와 백엔드 지원
기존 `InstallerUi`의 templateId/version·assets·strings/locales·pages·bindings를 유지한다. 레이아웃·색상 등 지원 속성은 버전이 지정된 템플릿 스키마에서 정의한다. `InstallationFlow`는 별도로 유지하며 각 화면이 직접 관리자 작업을 실행하지 않는다.
| 변경 | 기본 테마 | 사용자 정의 BA |
| --- | --- | --- |
| 로고·문구·약관·지원 스타일 | 고정 WiX 테마가 지원하는 속성으로 매핑 | 구현한 토큰/리소스 스키마로 지원 |
| 시안의 두 외형 전체 재현 | 페이지/컨트롤 계약에 대한 실험 필요, 동일 재현 보장 안 함 | 선택한 컴포넌트/레이아웃 구현 후 지원 |
| 임의 페이지·입력·분기 | 미지원이면 빌드 차단 | 페이지 그래프·입력 검증·무인 기본값 계약 필요 |
| 필수 백업/무결성 약화 | 불가 | 불가 |
| UAC 화면 변경 | 불가 | 불가 |
UI 수정은 새 스냅샷·빌드·서명·시험으로 배포한다. 목표 페이지 목록, 약관 버전, 입력 검증, 모드 분기, 선택 Feature가 달라지면 설치/패치 호환성도 다시 검사한다.
## 인수 기준과 작업 연결
- BL-15/30: 페이지와 실행 단계의 구분, 필수 단계 보호, 모드별 과정 검증.
- BL-24/27: 준비/적용/취소/실패/재부팅 상태와 승인된 설치·패치·복구 경로 연결.
- BL-31: 두 외형의 템플릿·문구·리소스 편집과 백엔드 지원 여부 표시.
- BL-32: 약관 조건·입력·페이지 분기·선택값 유지·설치 시작 후 계획 고정.
- BL-33: 5개 모드, 약관/경로/제거 확인, 이전 이동 시 선택값 보존, 준비 실패, 실행 앱, 적용 중 취소, 상태 불명확, 재부팅을 시험. DPI·키보드·스크린 리더·다국어 및 native 결과를 검증.
시안 작성은 위 구현 항목을 완료 처리하지 않는다. 신규 기능 ID를 늘리지 않고 미착수 BL-15/24/30~33의 완료 기준을 구체화한다. 일정은 단독 개발·AI 지원 없음 가정을 유지하며 M0/BL-08 실험과 M4 착수 시 기본 테마로 가능한 화면 범위와 사용자 정의 BA 공수를 다시 산정한다.
@@ -0,0 +1,95 @@
# 범용 설치 패키지 제작과 EXE·MSIX 출력
2026-10-19 실행 범위 변경: 이 문서의 “두 형식 모두 필수”는 장기 확장 구조다. 최신 사용자 요청에 따라 **MSIX를 먼저 완성하고 EXE는 후속**으로 이월한다. 두 형식 동시 완성을 첫 출시의 조건으로 사용하지 않는다. Store 제출 프로필도 후속이다. [현재 설계](12-msix-first-builder.md) 참조.
결정일: 2026-10-19. 사용자 의도에 따라 제품 범위를 정정한다. 이 문서는 기존 문서의 EG-BIM 전용 가정과 MSIX 후속 검토 분류를 대체한다. 아직 구현·설치 시험을 완료한 것은 아니다.
## 제품과 배포의 경계
개발 대상은 **여러 Windows 제품의 설치 프로젝트를 작성하고 EXE 또는 MSIX를 생성하는 범용 제작기**다. 제작기는 두 형식을 모두 필수 지원하며, 각 프로젝트는 EXE만, MSIX만, 두 형식을 함께 선택할 수 있다. EG-BIM은 기존 InstallShield 전환을 검증하는 적용 사례다. 제품명·제조사·경로·실행 파일·레지스트리·런타임·서명자는 프로젝트 입력이며 엔진에 고정하지 않는다.
Microsoft Store는 생성된 파일을 등록하는 배포 대상이다. Store 계정이나 등록 없이도 EXE와 직접 배포용 MSIX를 만들 수 있어야 한다. Store 심사 통과와 특정 고객의 전환 완료는 범용 제작기의 패키지 생성 성공 조건이 아니다.
```mermaid
flowchart TD
P[제품별 프로젝트와 원본 파일] --> C[Rust 공통 검증·스냅샷·출력 계획]
C --> E[EXE 백엔드: WiX MSI + Burn]
C --> M[MSIX 백엔드: Manifest + MakeAppx]
E --> EA[EXE와 필요한 MSI/CAB/런타임]
M --> MA[MSIX]
EA --> D[직접 배포·네트워크 배포]
MA --> D
EA --> S[Store 제출용 내보내기·검사]
MA --> S
S --> R[별도 Store 등록·심사·공개]
```
| 축 | 설정과 책임 |
| --- | --- |
| 출력 형식 `format` | `exe` 또는 `msix`; 제작기 필수 지원 |
| 전달 구성 `delivery` | EXE: `offline-singlefile`, `offline-layout`, `network`; MSIX: `package`, `appinstaller` |
| 배포 대상 `distribution` | `direct` 또는 `store`; 형식을 결정하지 않음 |
| 공개 정책 | 자체 서버 그룹 승인, Store 제출/공개 등 별도 운영 시스템의 책임 |
허용 조합은 EXE 직접 배포의 세 delivery, EXE Store의 `offline-singlefile`, MSIX 직접 배포의 `package/appinstaller`, MSIX Store의 `package`다. Store 대상의 `network`·`offline-layout`·`appinstaller`와 형식에 맞지 않는 조합은 계획 단계에서 차단한다.
`network`는 제3의 패키지 형식이 아니다. EXE는 원격 payload를 받는 부트스트래퍼, MSIX는 필요할 때 HTTPS 패키지를 참조하는 `.appinstaller`로 전달한다. `.appinstaller`는 설치 패키지 자체가 아닌 배포 설명 파일이다. [Microsoft App Installer 개요](https://learn.microsoft.com/en-us/windows/msix/app-installer/app-installer-file-overview)
## 백엔드와 최초 지원 범위
공통 `PackageBackend` 포트는 `capabilities`, `validate`, `plan`, `build`, `inspect` 계약을 제공한다. `WixBackend`와 `MsixBackend`를 주입하고 서명·저장·작업 취소는 공통 서비스가 조정한다. 프로젝트 검증과 실제 출력 검사 모두 백엔드별 규칙을 사용한다. 도구 버전과 해시는 잠금 파일에 고정한다.
| 기능 | EXE / WiX·Burn | MSIX |
| --- | --- | --- |
| 설치 위치 | 프로젝트 기본 경로와 허용된 사용자 선택 | OS 관리; 임의 설치 폴더 입력 제공 안 함 |
| 최종 사용자 화면 | 표준 테마, 사용자 정의 BA, 설치 단계 편집 | Windows/Store 설치 UI; manifest 표시명·아이콘 등 앱 메타데이터 편집 |
| 파일·설정 | 디렉터리·MSI 컴포넌트·명시적 레지스트리 | 패키지 내부 경로·지원 manifest 선언; 임의 HKLM 작업으로 변환하지 않음 |
| 런타임 | 번들에 포함하거나 검증된 원격 payload 사용 | 지원 프레임워크 의존성 또는 앱의 자체 포함 구성 검증 |
| 업데이트 | MSI 기준 상태별 MSP, 검증된 전체 업그레이드 | 동일 계열의 버전 증가 MSIX; 직접 배포/App Installer 또는 Store |
| 기존 설치 전환 | 명시적 탐지·백업·제거·복원 정책 | 별도 전환 계획; 기존 EXE 자동 제거를 가정하지 않음 |
MSIX 최초 범위는 패키징 가능한 일반 데스크톱 앱의 파일, 실행 항목, 로고, 최소 OS, 의존성, 허용 capability/extension 선언이다. 서비스·드라이버·임의 custom action·복잡한 CAD/COM 연동은 초기 지원 목록 밖으로 두고 지원되지 않는 설정을 빌드 전에 차단한다. 이는 모든 MSIX가 해당 기능을 원천 지원하지 않는다는 뜻이 아니다. 앱별 호환성을 검토하고 검증된 매핑을 추가한다. [MSIX 준비 사항](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-prepare)
MSIX는 완성된 설치 EXE를 확장자만 바꿔 만드는 결과물이 아니다. 제품 payload와 선언적 설정에서 각 형식을 생성한다. 기존 설치기를 실행해 변경을 캡처하는 자동 변환 기능은 별도 후속 범위다. 한 제품의 EXE 지원이 곧 그 제품의 MSIX 호환성을 의미하지 않는다.
## 프로젝트 v2 계약
공통 필드는 `schemaVersion`, `product`, `sources`, `files`, `formats`, `targets`다. 2026-10-27 결정으로 별도 프로젝트 ID를 제거했다. 제품 표시 버전은 공통으로 두되 실제 MSI/MSIX 버전은 형식별로 명시하고 검증한다. [설계용 예제](examples/generic.package-project.json)는 두 형식과 배포 대상을 독립 선택하는 구조를 보여 준다.
- `formats.exe`: MSI 버전·UpgradeCode·설치 범위·기본 경로, 레지스트리, 런타임, MSP 정책, `installationFlow`, `installerUi`.
- `formats.msix`: Identity Name·Publisher·Version·Architecture, 애플리케이션 실행 경로·로고, 최소 OS, 의존성과 capabilities/extensions. MSI GUID나 Burn 페이지를 요구하지 않는다.
- `targets[]`: 안정적인 id, format, delivery, distribution, signingProfileRef, 필요 시 불변 payload URL 또는 Store identity 참조. 네트워크 URL·인증서·Store 이름이 미확정이면 해당 대상만 계획에서 차단한다.
- `identity.lock.json`: EXE의 MSI/Burn 계열과 MSIX의 패키지 계열을 분리한다. MSIX Publisher 변경을 MSI UpgradeCode 교체로 처리하지 않는다.
- `ReleaseRecord.targets[]`: 대상별 실제 식별자·산출물·서명 결과·검증 증빙·상태를 저장한다. MSI/MSP 필드는 해당 형식에만 존재한다.
v1 EG-BIM 예제는 이전 EXE 전용 설계 입력으로 보존한다. v1→v2 가져오기는 백업과 변경 보고서를 만들고 EXE 필드를 이동한다. MSIX publisher나 Store identity를 임의 생성하지 않으며 사용자 입력 전에는 MSIX 대상을 활성화하지 않는다. `store-singlefile`은 `format=exe`, `delivery=offline-singlefile`, `distribution=store`로 명시적으로 변환한다. 알 수 없는 필드/버전과 손실되는 변환은 오류다.
제품 릴리스 하나에 EXE·MSIX를 함께 요청하면 출력 계획에 두 대상이 고정된다. 한쪽 실패는 전체 성공으로 표시하지 않는다. 성공한 대상은 개별 보존할 수 있으나 전체 요청은 `PartialFailed`로 기록하고 통합 배포 준비 상태를 부여하지 않는다. 실패 대상 재시도는 새 buildId를 사용하며 이미 확정한 파일을 덮어쓰지 않는다.
## 생성·서명·내보내기
공통 검증 → 입력 스냅샷 → 대상별 호환성 검사 → 백엔드 빌드 → 대상 정책에 따른 서명/검증 → 최종 해시·불변 저장 순서다.
EXE는 기존 MSI/CAB/Burn 생성·서명 파이프라인을 사용한다. MSIX는 파일 매핑과 `AppxManifest.xml` 생성 → SDK manifest/자산 검사 → `MakeAppx pack` → 패키지 내용/identity 검사 → 대상별 서명 → 검증 순서다. MakeAppx의 검증을 우회해 성공 처리하지 않는다. `.msix` 생성이 최초 필수이며 다중 아키텍처 `.msixbundle`은 확장 범위다. MakeAppx만으로 `.msixupload`까지 생성된다고 가정하지 않는다. [MakeAppx 공식 문서](https://learn.microsoft.com/en-us/windows/msix/package/create-app-package-with-makeappx-tool)
직접 배포 MSIX는 대상 PC가 신뢰할 수 있는 인증서로 서명하고, manifest Publisher와 인증서 Subject 일치를 검증한다. 개발 인증서 결과와 일반 사용자 배포 준비 상태를 구분한다. 개인 키는 패키지에 포함하지 않는다. [MSIX 인증서 요건](https://learn.microsoft.com/en-us/windows/msix/package/create-certificate-package-signing)
Store MSIX 출력은 Partner Center의 제품 identity에 맞춘 제출용 결과로 관리한다. Store의 배포 서명은 이후 단계이므로 서명되지 않은 제출 파일을 직접 설치 가능으로 표시하지 않는다. 직접 배포용 서명본과 제출용 파일은 대상별 해시로 구별한다. Store는 `.msix` 등의 패키지를 받으며 제출·심사·공개는 제작기 빌드 상태와 별도다. [Store 업로드 형식](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/upload-app-packages)
Store EXE 출력은 단일 완전 오프라인 설치 파일·무인 인자·서명·고정 HTTPS URL 등 제출 요건을 추가 검증한다. 직접 배포용 `offline-layout`이나 `network` 성공만으로 Store 준비 완료를 선언하지 않는다. [Store EXE 패키지 요건](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msi/app-package-requirements)
## 버전·업데이트·공존
MSI의 3부분 버전/GUID/MSP 정책은 EXE 백엔드 전용이다. MSIX는 4부분 숫자 버전을 검증한다. Store 대상은 첫 부분 0 금지와 마지막 부분 0 등 Store 버전 요건도 적용한다. 공통 표시 버전 문자열을 무검증으로 복사하지 않는다. [Store MSIX 버전 규칙](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/app-package-requirements)
MSIX 업데이트에 MSP를 생성하거나 설치 폴더에 자체 패치를 덮어쓰지 않는다. 버전 증가 패키지를 제공하고 Windows 배포 기능을 이용한다. 직접 배포의 App Installer 설정과 Store 관리 업데이트는 별도 정책이다. 재설치·제거·업데이트 실패와 앱 데이터 보존을 각각 VM에서 확인한다.
동일 제품의 EXE↔MSIX는 자동 상호 업그레이드가 아니다. 기본은 서로 독립된 설치로 취급하고 공존 허용 여부, 앱 데이터 소유권, 파일 연결, 실행 항목 충돌을 프로젝트에서 결정한다. 전환 기능을 제공하려면 기존 제품 탐지·백업·복원·실패 복구를 별도 검증한다. Publisher/Store identity 차이도 자동 통합하지 않는다.
## 제작 화면과 완료 기준
제작기는 공통 제품·파일 화면 뒤에 출력 대상 편집을 제공한다. EXE 탭에서는 설치 경로·레지스트리·과정/마법사·MSP를, MSIX 탭에서는 manifest·앱 자산·identity·capabilities·업데이트 정책을 편집한다. 출력 대상별 미지원 설정을 이유와 필드 위치로 표시하며 조용히 무시하지 않는다. MSIX 선택 화면에 EXE 설치 경로/마법사 편집을 그대로 노출하지 않는다.
최소 두 개의 독립된 가상 제품으로 이름·게시자·실행 파일·경로가 하드코딩되지 않았음을 검증한다. 각 제품에서 EXE와 MSIX 생성, 직접 설치, 실행, 업데이트, 제거, 데이터 보존을 시험한다. Store 계정·자체 배포 서버 없이 이 시험을 수행할 수 있어야 한다. 잘못된 Publisher·누락 런타임/자산·미지원 기능·한쪽 출력 실패도 검사한다. EG-BIM의 실제 전환과 Store 제출은 별도 적용 증빙으로 관리한다. 계정 없는 공통 시험에서는 가상 Store identity로 제출 규칙과 오류 처리를 검증한다. 실제 Partner Center identity가 확인되지 않은 출력은 `StoreSubmissionReady`로 표시하지 않는다.
구현은 [BL-46~53](../backlog/README.md), 일정은 [단독 개발 기준선 v2](../plan/10-development-schedule.md), 변경 사유는 [변경 기록](../changes/2026-10-19-general-package-builder.md)을 따른다.
+79
View File
@@ -0,0 +1,79 @@
# MSIX 우선 제작기 — 첫 버전 설계와 화면
결정일: 2026-10-19. 사용자의 “우선 MSIX를 만들어주는 프로그램을 개발” 요청에 따른 현재 기준이다. [11번 설계](11-generic-exe-msix-architecture.md)의 범용 구조를 유지하되 두 형식 동시 완성 요구를 **MSIX 먼저, EXE는 후속**으로 변경한다. Rust/Tauri 앱과 실제 패키징 엔진은 아직 구현하지 않았다.
## 첫 제품의 범위
2026-10-26 명칭·파일 형식 결정: 정식 프로그램 명칭은 **Installer Studio**, 프로젝트 파일은 **`.isproj` (JSON v2)**만 지원한다. 아래 작업용 이름은 최초 시안의 기록이다. [파일 형식 규칙](13-isproj-project-format.md)이 파일명·열기·저장에 관한 이전 예시에 우선하며 기존 JSON 호환은 제공하지 않는다.
첫 제품은 Windows 앱의 배포 파일과 메타데이터를 입력받아 직접 배포 가능한 `.msix`를 만드는 **범용 MSIX 제작기**다. 디자인의 `MSIX Studio`는 작업용 이름이다. EG-BIM에 종속되지 않으며 Store 계정이나 자체 배포 서버 구축 없이 패키지를 만들 수 있어야 한다.
| 1차 필수 | 완료 조건 |
| --- | --- |
| 제품별 프로젝트 | `.isproj` (JSON v2) 전용 열기/저장, 기존 JSON 미지원, 앱 이름·게시자·버전·파일·실행 항목·자산 설정 |
| 입력과 manifest | 파일 포함/제외·경로 충돌·필수 자산·아키텍처·최소 OS·의존성·허용 선언 검증 |
| MSIX 생성 | Windows SDK MakeAppx, 결과 내용/identity 확인, 원본 무변경 |
| 서명 | 개발/운영/미서명 상태 구별, Publisher와 인증서 Subject 검증, 서명 후 최종 해시 |
| 작업/내보내기 | 취소·실패·재시도, 불변 결과·로그·보고서·도구 버전 보존 |
| 직접 배포 업데이트 | 선택적으로 `.appinstaller` 생성, HTTPS 경로·버전·업데이트 정책 검사와 VM 시험 |
| GUI와 CLI | 같은 검증/빌드 계약, 제품별 파일·패키지·서명·업데이트 화면 |
| 검증 | 두 독립 가상 제품의 MSIX 설치·실행·상위 버전 업데이트·제거·데이터 보존 증빙 |
최초 범위는 단일 앱 실행 항목, 한 대상당 단일 아키텍처의 `.msix`다. 지원 OS/CPU의 정확한 조합은 MS0에서 고정한다. 시안의 x64/ARM64와 Windows 버전은 선택 예시이며 지원 인증 결과가 아니다. 자산 파일·스키마·런타임 의존성은 실제 SDK 및 VM에서 검증한다.
후속 범위는 EXE/MSI/MSP/Burn, HTML 설치 마법사/사용자 정의 BA, 설치 과정 핸들러, EG-BIM legacy 전환, 자체 인증/그룹 배포 서버, Store 제출용 프로필·계정 연결·등록, MSIX bundle, 기존 설치기 캡처/자동 변환이다. 기존 작업은 삭제하지 않고 이월한다. **MSIX 제작기 v1 완료에 EXE 생성이나 두 형식 동시 회귀를 요구하지 않는다.**
## 구조와 생성 계약
```mermaid
flowchart LR
UI[Tauri 제작 화면] --> E[Rust 공통 엔진]
CLI[CLI] --> E
E --> V[프로젝트·파일·manifest 검사]
V --> B[MsixBackend / MakeAppx]
B --> S[서명·패키지 검증]
S --> R[불변 MSIX·보고서·선택 App Installer]
```
`PackageBackend` 추상화는 유지하되 첫 구현은 `MsixBackend`만 제공한다. WixBackend 구현·WiX 설치·MSI GUID 발급은 첫 제품의 의존성이 아니다. v2 모델에서 `formats.exe`는 생략 가능하며 MSIX-only 프로젝트에 EXE 설정을 요구하지 않는다. 기존 EXE/v1 프로젝트는 지원 불가 사유와 후속 가져오기 안내를 표시하고 조용히 변환하지 않는다. [MSIX-only 예제](examples/msix.package-project.json)는 미확정 설계 입력이다.
공통 입력 스냅샷 → manifest/자산/의존성 검사 → `MakeAppx pack` → 실제 파일 내용/identity 추출 → 서명/검증 → 최종 해시·불변 저장 순서다. MakeAppx의 검증을 끄지 않는다. `.msixupload` 생성은 MakeAppx의 기본 산출물이라고 표시하지 않는다. [Microsoft MakeAppx 문서](https://learn.microsoft.com/en-us/windows/msix/package/create-app-package-with-makeappx-tool)
GUI/CLI는 `validate`, `plan release --target <id>`, `build --plan-id <id>`, `job status`, `export` 계약을 공유한다. v1은 `distribution=direct`, `delivery=package|appinstaller`만 지원한다. EXE·Store·bundle 요청은 `UnsupportedTarget`으로 차단한다. 다중 MSIX 대상은 직렬 실행하고 부분 실패를 전체 성공으로 표시하지 않는다.
서명은 OS 인증서 저장소 또는 사전 설정한 서명 공급자 프로필을 참조한다. 일반 배포 시 신뢰·Publisher 일치를 확인한다. 개발 인증서 신뢰 설정은 별도 시험 환경에서 수행하며 제작기가 사용자 PC에 인증서를 몰래 설치하지 않는다. 미서명 개발 결과의 보존은 가능하되 직접 설치/배포 준비 상태는 부여하지 않는다.
상태는 `Draft → Planned → Running → Built → ArtifactVerified → Finalized`이며 실패·취소·중단을 구분한다. 서명/해시 검사 성공과 VM 설치 시험 성공은 별도다. `DeploymentReady`는 해당 아티팩트 해시의 실제 증빙이 있어야 한다. 시안의 성공 표시·가상 파일 목록은 어떤 구현 작업의 완료 증빙도 아니다.
## 업데이트와 설치 경계
제작기는 상위 버전 전체 MSIX와 필요 시 `.appinstaller`를 만든다. MSIX는 블록 정보를 이용하는 Windows 차등 업데이트 경로를 활용하며 별도 MSP나 자체 차분 파일을 만들지 않는다. 전체 파일을 먼저 내려받는 자체 다운로드 코드에 전송 절감이 자동 생긴다고 가정하지 않는다. [Microsoft 차등 업데이트](https://learn.microsoft.com/en-us/windows/msix/desktop/managing-your-msix-deployment-update)
업데이트 설정은 사용 여부, 안내 파일 URI, 버전별 불변 MSIX URI, 실행 시 확인 간격, 사용자 안내, 필요 시 실행 전 업데이트 요구를 명시한다. Windows/스키마 지원 범위를 검사한다. 직접 설치만 선택하면 자동 업데이트가 연결된 것으로 표시하지 않는다. 패키지를 버전별 새 URL로 게시하고, App Installer 안내 파일은 명시적 갱신·이력 보존 정책을 사용한다. 업로드 자체는 첫 제품 외부의 수동 작업이다. [Store 밖 자동 업데이트](https://learn.microsoft.com/en-us/windows/msix/app-installer/auto-update-and-repair--overview)
설치 위치는 Windows 관리로 표시하고 임의 설치 폴더 입력을 제공하지 않는다. 사용자 데이터 폴더 선택, 개인정보 동의 수집·기록·기능 제한은 대상 앱 첫 실행 등 앱 자체의 책임이다. 패키지에 HTML을 넣었다고 설치 중 실행되거나 동의가 기록되는 것으로 간주하지 않는다. 공통 HTML 초기 설정 런타임은 후속 별도 설계다.
App Installer의 제한된 XML 외형 커스터마이징과 Store 표시 정보는 HTML 설치 페이지 편집과 다르다. 첫 버전은 표준 표시 메타데이터·앱 자산까지만 제공하며 XML UX 커스터마이징은 후속 검증 범위다. [App Installer UX](https://learn.microsoft.com/en-us/windows/msix/app-installer/how-to-create-custom-app-installer-ux)
## 제작기 디자인
[상호작용 시안](../../output/msix-builder-concept.html), [브라우저 미리보기](../../output/msix-builder-preview.html). 시안은 두 레이아웃을 비교하며 제품 이름, 버전, 파일, Publisher, 서명, 업데이트 설정을 로컬 예제 상태에서 바꿀 수 있다. 실제 파일을 읽거나 MakeAppx/서명/설치를 실행하지 않는다.
| 화면 | 제작자가 설정하는 값 | 피드백 |
| --- | --- | --- |
| 앱 정보 | 표시명·설명·게시자 표시명·4부분 버전·아키텍처·아이콘 | 앱 표시 정보 미리보기; 실제 Windows 설치 화면과 구별 |
| 파일 구성 | 소스·포함/제외·패키지 상대 경로·실행 파일 | 포함 목록·충돌·누락·용량; 시안은 예제 파일 추가/제거 |
| 패키지 설정 | Identity Name·Publisher·최소 OS·앱 선언·의존성 | manifest 요약, 지원하지 않는 시스템 작업 안내 |
| 서명 | OS/공급자 프로필·개발/운영/미서명 | Publisher 불일치 오류, 대상 PC 신뢰와 개발 인증서 구분 |
| 업데이트 | 직접 배포·App Installer 사용·URI·확인 간격·안내 | 생성될 파일 목록; Store와 서버 구축은 후속 |
| 검증·빌드 | 검사·생성·취소·로그·결과 내보내기 | 단계별 상태, 오류 설정 이동, 서명/설치 시험의 독립 상태 |
**프로젝트 작업실형**은 왼쪽 고정 탐색, 중앙 편집, 오른쪽 미리보기/진단을 사용한다. 반복해서 제품 버전을 만드는 작업에 맞춘 기본 제안이다. **단계별 제작형**은 상단 여섯 단계와 이전/다음 흐름으로 처음 패키지를 만드는 사용자를 안내한다. 두 외형은 동일한 필드·검증·결과를 사용하며 제품 출시 시 두 화면을 모두 구현해야 한다는 요구가 아니다. MS5 착수 전에 하나를 선택한다.
상태 시나리오는 정상 설정, 잘못된 버전, 실행 파일 누락, Publisher/인증서 불일치, 잘못된 업데이트 URI, 미서명/개발 서명, 검사 후 설정 변경이다. 시안은 즉시 완료되는 모의 빌드를 사용한다. 실제 빌드는 도구 이벤트와 취소/중단 복구를 연결해야 한다. 출력 폴더 선택, 실파일 가져오기, 자산 크기 검사, 런타임 의존성 편집·검증, 서비스 서명은 시안에 구현되어 있지 않다.
## 완료 기준과 추적
첫 출시 범위는 [백로그](../backlog/README.md)의 1차 18개 항목과 [일정 v3](../plan/10-development-schedule.md)의 MS0~MS6다. EXE·Store·EG-BIM 업무 시험이 공통 MSIX 경로를 차단하지 않도록 의존성을 분리한다. SDK 누락, 잘못된 인증서/identity, 파일 누락, 디스크 부족, 취소/중단, 서버 연결 실패, 실행 중 업데이트, 버전 건너뛰기, 사용자 데이터 보존과 제거를 시험한다.
문서와 디자인 검증은 링크·JSON·의존성·브라우저 렌더링/상호작용까지다. 실제 배포 준비 판정은 구현 후 별도 Windows VM과 실제 서명으로 수행한다. [변경 기록](../changes/2026-10-19-msix-first.md)
@@ -0,0 +1,68 @@
# Installer Studio 프로젝트 파일 형식
결정일: 2026-10-26. Installer Studio의 기본 프로젝트 확장자는 **`.isproj`**다. GUI·CLI의 생성·열기·저장에 적용했다. 사용자 요청에 따라 기존 JSON 프로젝트 호환·변환은 제공하지 않는다.
## 형식과 용도
`.isproj`는 설치 패키지 제작 설정을 보관하는 UTF-8 JSON 파일이다. ZIP·XML·실행 파일이 아니며, 내부 데이터는 기존 `PackageProject v2`와 `schemaVersion: 2`를 유지한다. 확장자 변경만으로 스키마 버전을 올리지 않는다.
예: `EG-BIM.isproj`, `BaronViewer.isproj`. MSIX 전용 확장자로 제한하지 않으며 후속 EXE/MSI 제작에도 같은 형식을 사용한다. 최종 배포 파일인 `.msix`, `.appinstaller`, `.exe`, `.msi`, `.msp`와 구분한다.
[MSIX 프로젝트 예제](examples/msix.isproj)는 기존 v2 예제의 내용을 유지하고 확장자만 적용한 설계용 파일이다. 미확정 값이 포함되어 있으므로 그대로 배포용 패키지를 만들 수 있다는 의미는 아니다.
2026-10-27 결정: 별도의 `projectId` 필드를 화면과 저장 모델에서 제거했다. 제작기는 파일 경로로 프로젝트를 열고, MSIX 앱 계열은 `formats.msix.identity.name`과 `publisher`로 식별한다. 파일 이름이나 앱 표시 이름을 바꾸어도 MSIX Identity를 자동 변경하지 않는다. 향후 MSI 식별자도 별도로 관리한다. 새로운 제품으로 복제하는 기능은 별도의 명시적 작업으로 설계한다.
개발 중인 v2 계약에서 필드를 제거했으며 기존 호환·자동 변환은 제공하지 않는다. `projectId`가 남아 있는 파일은 알 수 없는 필드 오류로 거부한다. 사용자 파일과 기존 빌드 이력을 일괄 수정하지 않는다.
## 파일과 작업 폴더
```text
project/
EG-BIM.isproj # 공유 가능한 제품/패키지 설정
EG-BIM.isproj.local.json # 해당 PC의 소스·SDK·서명 프로필 참조
EG-BIM.isproj.bak # 직전 프로젝트 저장본
EG-BIM.isproj.local.json.bak # 직전 로컬 설정 저장본
identity.lock.json
toolchain.lock.json
.work/
releases/
patches/
attestations/
```
로컬 설정과 백업 이름은 **전체 프로젝트 파일명에 접미사를 추가**하여 계산한다. 예를 들어 `EG-BIM.isproj`에 `.local.json` 또는 `.bak`를 붙인다. `.isproj`를 `.json.bak`로 치환하지 않는다. 로컬 설정·백업·인증서 개인 키는 형상 관리에서 제외한다. 프로젝트에는 개인 키나 비밀번호를 저장하지 않는다.
현재 디렉터리 단위의 작업·릴리스·lock 구조를 유지하므로 **작업 폴더 하나에 프로젝트 하나**를 둔다. 다른 이름으로 저장은 별도 작업 폴더를 사용하며, 기존 이력 폴더를 자동 복사하거나 다른 프로젝트와 합치지 않는다. 상대 경로는 대상 폴더 기준으로 다시 계산하고, 연결이 깨지는 소스는 저장 전에 안내한다.
## 생성·열기·저장
| 동작 | 설계 규칙 |
| --- | --- |
| 새 프로젝트 | 기본 파일명 `InstallerProject.isproj`, 필터 `Installer Studio 프로젝트 (*.isproj)` |
| 프로젝트 열기 | `.isproj` 전용 필터. 기존 `.json` 파일은 공통 로더에서도 거부한다. |
| 확장자 처리 | `.isproj`는 대소문자 구분 없이 허용한다. 저장 이름에 확장자가 없으면 `.isproj`를 붙이고, 다른 확장자가 있으면 수정을 안내한다. `.isproj.isproj` 중복 생성을 금지한다. |
| 내용 검사 | 확장자뿐 아니라 JSON 파싱, `schemaVersion`, 필수 필드와 지원 대상 검증을 수행한다. 일반 JSON·상위 스키마·손상 파일은 프로젝트로 취급하지 않는다. |
| 저장 | `expectedRevision` 충돌 검사, 백업, 같은 볼륨의 임시 파일 쓰기와 원자적 교체를 사용한다. 실패 시 원본을 유지한다. |
| 최근 프로젝트 | 실제 `.isproj` 경로를 저장한다. 기존 JSON 항목은 숨기고 해당 ID로도 열지 못하게 한다. 기존 파일은 삭제하지 않는다. |
| CLI | `--project .\EG-BIM.isproj`를 표준 예제로 사용한다. GUI와 같은 로더·검증기를 공유한다. |
Windows 탐색기 연결은 후속 구현 항목이다. `.isproj`에 Installer Studio 아이콘과 열기 동작을 연결하고, 더블 클릭으로 받은 경로는 일반 프로젝트 열기와 같은 검증 및 미저장 변경 보호를 거친다. 경로를 셸 명령으로 실행하지 않는다. `.json` 전체의 기본 연결은 변경하지 않는다.
## 기존 형식 미지원과 구현 범위
기존 `.json` 프로젝트는 GUI·CLI·최근 목록을 포함한 모든 열기 경로에서 거부한다. 확장자를 생략한 새 프로젝트 이름에만 `.isproj`를 붙인다. `.json`을 지정한 생성 요청은 자동 변경하지 않는다.
기존 `package-project.local.json`으로 폴백하지 않는다. `.isproj.local.json`이 없으면 빈 로컬 설정으로 열고 소스·도구를 사용자가 연결한다. 기존 사용자 파일과 이력은 삭제하거나 이름을 바꾸지 않는다. 실행용 fixture와 새 샘플 생성 스크립트는 `.isproj`로 갱신했다.
일반 저장은 로컬 설정을 먼저 원자적으로 교체하고 프로젝트 교체가 실패하면 로컬 설정을 복구한다. 각 파일의 교체는 원자적이지만 프로세스 강제 종료·전원 차단까지 포함한 두 파일의 트랜잭션은 보장하지 않는다. 직전 `.bak` 파일을 보존한다.
탐색기 연결, 다른 이름으로 저장, 작업 폴더당 프로젝트 하나의 강제 검사는 후속 항목이다. 현재는 새 프로젝트와 일반 열기·저장을 지원한다.
## 구현 시 확인할 항목
- GUI의 생성·열기·저장 필터와 CLI가 `.isproj`를 같은 방식으로 처리하는지 확인한다.
- 한글·공백이 있는 파일명, 대문자 확장자, 확장자 생략·중복 입력을 확인한다.
- 기존 JSON 거부, 기존 로컬 설정 미참조, `.isproj`의 소스·SDK·서명 참조 저장을 확인한다.
- 프로젝트 ID 없이 생성·저장·열기·빌드가 가능하고, 일반 저장 전후 패키지 identity가 바뀌지 않는지 확인한다.
- 백업 이름, 외부 변경 충돌, 중간 저장 실패, 상대 경로 및 최근 목록 갱신을 확인한다.
- 탐색기 연결은 앱 배포 방식별 등록·제거와 경로 인자 전달을 별도 검증한다.
+24 -11
View File
@@ -1,13 +1,16 @@
# 설치 패키지 자동화 생성 프로그램 설계
작성 기준: 2026-10-16. 대상 제품: EG-BIM/Baron. 구현 언어는 **Rust**, 데스크톱 화면은 **Tauri 기반 웹 UI**로 확정한다. 이 문서들은 구현 전 설계이며 실행 가능한 프로그램이나 검증된 설치 패키지는 아직 없다.
작성 기준: 2026-10-16. 2026-10-19 범위 정정: **범용 Windows 설치 패키지 제작기**이며 EG-BIM/Baron은 적용 사례다. 구현 언어는 **Rust**, 데스크톱 화면은 **Tauri 기반 웹 UI**로 확정한다. 이 문서들은 구현 전 설계이며 실행 가능한 프로그램이나 검증된 설치 패키지는 아직 없다.
프로그램은 설치 파일 목록, 설치 위치, 레지스트리, 버전·GUID를 프로젝트로 관리하고, 전체 릴리스와 기준 버전별 패치를 생성한다. GUI와 CI용 CLI가 같은 Rust 엔진을 사용한다. 패키지 생성 백엔드는 기존 검토를 이어 **WiX MSI/MSP + Burn EXE**를 기준으로 설계한다.
현재 첫 버전은 **MSIX 제작기**다. GUI와 CI용 CLI가 같은 Rust 엔진과 **Windows SDK MakeAppx + manifest** 백엔드를 사용한다. EXE/MSI/MSP/Burn·HTML 설치 마법사는 후속 단계로 유지한다. Store 제출도 후속이며 직접 배포에 Store 계정을 요구하지 않는다. [12번 MSIX 우선 설계](12-msix-first-builder.md)가 현재 범위/완료 기준이고 [11번](11-generic-exe-msix-architecture.md)은 장기 두 형식 확장 구조다.
## 문서 구성
2026-10-26 추가 결정: 프로그램 명칭은 **Installer Studio**, 프로젝트 확장자는 **`.isproj`**다. 내부 형식은 JSON v2를 유지하며 기존 JSON 호환은 제공하지 않는다. [13번 파일 형식 설계](13-isproj-project-format.md)를 파일 다이얼로그·저장소에 적용했으며 탐색기 연결은 후속 작업이다.
| 문서 | 설계 내용 |
| --- | --- |
| [프로그램 설계서 — Word](../program-design/MSIX-프로그램-설계서.docx) | 참고 Google Docs 양식을 적용한 MSIX 우선 통합 설계서: 기능 정의·흐름도·화면·인터페이스·검증·일정 |
| [01 구조와 모듈](01-architecture.md) | Tauri, Rust 공통 엔진, CLI, 작업 실행과 저장 구조 |
| [02 프로젝트와 데이터 모델](02-project-model.md) | 설정 파일, 파일 목록, 릴리스 기록, 예제 JSON |
| [03 파일·설치 위치·레지스트리](03-files-and-registry.md) | 파일 수집, 컴포넌트, 설치 경로, 레지스트리 작성 규칙 |
@@ -17,29 +20,39 @@
| [07 화면과 명령 인터페이스](07-ui-and-commands.md) | 화면별 기능, Tauri IPC, 작업 이벤트, CLI |
| [08 검증과 구현 순서](08-validation-and-roadmap.md) | 자동 검증, Windows VM 시험, 단계별 완료 기준 |
| [09 설치 과정과 UI 편집](09-install-flow-and-ui.md) | 단계 추가·삭제·순서·조건, UI 테마, 사용자 정의 Burn 화면 |
| [프로젝트 예제](examples/egbim.package-project.json) | 설계용 초기 프로젝트 설정 |
| [10 설치 마법사 재설계](10-installer-wizard-redesign.md) | InstallShield/Inno Setup 패턴, 5개 모드, 페이지·취소·실패·재부팅 및 편집 화면 계약 |
| [12 MSIX 우선 제작기](12-msix-first-builder.md) | 현재 1차 범위, 생성 계약, 제작 화면, 검증 기준 |
| [13 프로젝트 파일 형식](13-isproj-project-format.md) | `.isproj`, JSON v2 내용, 로컬 설정·백업 이름, 기존 JSON 거부와 GUI/CLI 규칙 |
| [MSIX-only 예제](examples/msix.package-project.json) | EXE 설정이 없는 현재 설계용 입력 |
| [11 범용 EXE·MSIX 구조](11-generic-exe-msix-architecture.md) | 제품 범위, 출력/배포 분리, MSIX 계약과 완료 기준 |
| [범용 프로젝트 예제 v2](examples/generic.package-project.json) | 두 형식을 함께 요청하는 미확정 설계 입력 |
| [EG-BIM 프로젝트 예제 v1](examples/egbim.package-project.json) | 이전 EXE 전용 초안; v2 가져오기 대상 |
## 범위와 결정
| 구분 | 내용 |
| --- | --- |
| 필수 | 파일·폴더 등록, 포함/제외, 설치 경로, 레지스트리, 버전/GUID, 전체 릴리스, 버전별 MSP |
| 필수 | PE·MSI·MSP·Burn 서명 연동, 2GB 초과 전체 용량 대응, 기준 릴리스 보존, 검증 보고서 |
| 필수 | 제작자의 설치 과정 추가·삭제·조건 편집, 설치 UI 커스터마이징; 표준 테마와 사용자 정의 BA의 지원 범위 구분 |
| 1차 필수 | 공통 제품/파일 설정, MSIX 생성, 서명·검증·불변 보관·내보내기·GUI/CLI |
| 후속 — EXE | 설치 경로, 레지스트리, 버전/GUID, 전체 릴리스, 버전별 MSP |
| 1차 필수 — MSIX | manifest/identity/자산/의존성, MakeAppx 생성, 직접 배포 서명, 버전별 업데이트 |
| 후속 — EXE | PE·MSI·MSP·Burn 서명 연동, 대용량 CAB, MSP 기준 보존 |
| 후속 — EXE | 제작자의 설치 과정 추가·삭제·조건 편집, 설치 UI 커스터마이징; 표준 테마와 사용자 정의 BA의 지원 범위 구분 |
| 확정 | Windows 대상, InstallShield 중단, Rust + Tauri |
| 설계 제안 | Tauri 2, TypeScript + React + Vite, JSON 프로젝트, WiX/Burn 백엔드 |
| 설계 제안 | Tauri 2, TypeScript + React + Vite, JSON v2, MakeAppx/SignTool; WiX/Burn은 후속 |
| 연계 | 업로드용 메타데이터 내보내기, 개발자→1차→2차→사용자 배포에 동일 아티팩트 사용 |
| 별도 시스템 | 배포 서버·그룹 승인, EG-BIM의 실제 자동 업데이트 실행부, Store 제출 |
| 후속 검토 | MSIX 출력, 클라우드 업로드 어댑터, 기존 ISM의 자동 가져오기 |
| 후속 검토 | MSIX 다중 아키텍처 bundle, 클라우드 업로드 어댑터, 기존 ISM의 자동 가져오기 |
Tauri는 **생성 프로그램의 화면과 데스크톱 실행 환경**이다. Tauri가 자체 앱을 배포하는 번들러와 EG-BIM 패키지를 만드는 엔진은 별개다. EG-BIM을 Tauri 앱으로 바꾸거나, Tauri 업데이터를 기존 제품에 바로 적용한다는 뜻이 아니다.
Tauri는 **생성 프로그램의 화면과 데스크톱 실행 환경**이다. Tauri가 제작기 자체를 배포하는 번들러와 고객 제품의 EXE/MSIX를 만드는 엔진은 별개다. EG-BIM을 Tauri 앱으로 바꾸거나, Tauri 업데이터를 기존 제품에 바로 적용한다는 뜻이 아니다.
기존 설치 기본 경로 `C:\Program Files\Baron\EG-BIM`을 유지한다. 실제 설치 파일 목록은 별도로 확정해야 하며 `D:\egbim\repo\14.1` 전체를 배포 파일로 취급하지 않는다. InstallShield 제거 스크립트가 사용자 데이터를 지울 수 있으므로 최초 전환에는 백업·복원 검증이 필요하다. [기존 현황](../plan/01-requirements-and-current-state.md), [설치기 전환](../plan/02-installer-migration.md)
EG-BIM EXE 적용 사례에서는 기존 설치 기본 경로 `C:\Program Files\Baron\EG-BIM`을 유지한다. 실제 설치 파일 목록은 별도로 확정해야 하며 `D:\egbim\repo\14.1` 전체를 배포 파일로 취급하지 않는다. InstallShield 제거 스크립트가 사용자 데이터를 지울 수 있으므로 최초 전환에는 백업·복원 검증이 필요하다. [기존 현황](../plan/01-requirements-and-current-state.md), [설치기 전환](../plan/02-installer-migration.md)
전체 설치 결과는 단일 EXE로 한정하지 않는다. 대용량 기본 출력은 `Setup.exe + MSI + 분할 CAB + 필수 런타임` 레이아웃이며, 네트워크용 EXE는 필요한 데이터를 내려받는다. Store용 단일 오프라인 설치 파일은 용량과 제출 요건을 통과한 경우에만 생성한다.
EXE 출력의 전체 설치 결과는 단일 파일로 한정하지 않는다. 대용량 기본 출력은 `Setup.exe + MSI + 분할 CAB + 필수 런타임` 레이아웃이며, 네트워크용 EXE는 필요한 데이터를 내려받는다. 직접 배포용 단일 오프라인 EXE도 지원하며, Store 대상은 제출 요건을 추가 검사한다. MSIX는 Store와 관계없이 별도 백엔드에서 생성한다.
## 완료된 설계와 남은 확정 사항
문서의 타입·명령·파일 형식은 구현 계약 제안이다. 도구의 정확한 버전, 제조사 표시명, 실제 배포 소스 폴더, 필수 런타임 목록, 서명 프로필, 지원 기준 릴리스는 첫 구현 단계에서 확정한다. 값이 비어 있거나 검증되지 않은 프로젝트는 편집·미리보기만 허용하고 정식 릴리스 확정은 차단한다.
기존 운영 정책은 [배포 계획](../plan/README.md)을 따른다. 이 설계에서는 생성 프로그램의 책임과 출력 계약을 구체화한다.
2026-10-19 검토에서 통합 구현 순서를 [백로그](../backlog/README.md)와 [단독 개발 일정](../plan/10-development-schedule.md)으로 정리했다. AI 지원 없이 개발자 1명이 제작기·업데이터·서버·검증을 수행하는 기준이며, 현재는 MSIX 작은 빌드·서명·업데이트 실험을 초기에 배치하고 전환/BA 실험은 후속으로 이월한다.
@@ -0,0 +1,36 @@
{
"schemaVersion": 2,
"product": {
"displayName": "Sample Desktop Tool",
"manufacturer": "Example Publisher",
"displayVersion": "1.0.0"
},
"sources": [{ "alias": "appPayload" }],
"files": [{ "id": "application", "source": "appPayload", "include": ["**/*"], "exclude": [], "targetRelativePath": "", "required": true }],
"formats": {
"exe": {
"msiVersion": "1.0.0",
"architecture": "x64",
"upgradeCode": null,
"installScope": "perMachine",
"installRoot": "[ProgramFiles64Folder]ExamplePublisher\\SampleDesktopTool",
"registry": [],
"prerequisites": [],
"patchPolicy": { "enabled": true },
"installationFlow": { "template": "standard" },
"installerUi": { "template": "standard-wizard" }
},
"msix": {
"identity": { "name": "ExamplePublisher.SampleDesktopTool", "publisher": null, "version": "1.0.0.0", "architecture": "x64" },
"minimumOsVersion": "10.0.19041.0",
"applications": [{ "id": "App", "executable": "SampleTool.exe", "entryPoint": "Windows.FullTrustApplication", "square44x44Logo": "Assets/Logo44.png", "square150x150Logo": "Assets/Logo150.png" }],
"capabilities": ["runFullTrust"],
"extensions": [],
"dependencies": []
}
},
"targets": [
{ "id": "exe-direct", "format": "exe", "delivery": "offline-singlefile", "distribution": "direct", "signingProfileRef": null },
{ "id": "msix-direct", "format": "msix", "delivery": "package", "distribution": "direct", "signingProfileRef": null }
]
}
+59
View File
@@ -0,0 +1,59 @@
{
"schemaVersion": 2,
"product": {
"displayName": "Sample Desktop Tool",
"manufacturer": "Example Publisher",
"displayVersion": "1.0.0"
},
"sources": [
{
"alias": "appPayload"
}
],
"files": [
{
"id": "application",
"source": "appPayload",
"include": [
"**/*"
],
"exclude": [],
"targetRelativePath": "",
"required": true
}
],
"formats": {
"msix": {
"identity": {
"name": "ExamplePublisher.SampleDesktopTool",
"publisher": null,
"version": "1.0.0.0",
"architecture": "x64"
},
"minimumOsVersion": "10.0.19041.0",
"applications": [
{
"id": "App",
"executable": "SampleTool.exe",
"entryPoint": "Windows.FullTrustApplication",
"square44x44Logo": "Assets/Logo44.png",
"square150x150Logo": "Assets/Logo150.png"
}
],
"capabilities": [
"runFullTrust"
],
"extensions": [],
"dependencies": []
}
},
"targets": [
{
"id": "msix-direct",
"format": "msix",
"delivery": "package",
"distribution": "direct",
"signingProfileRef": null
}
]
}
@@ -0,0 +1,59 @@
{
"schemaVersion": 2,
"product": {
"displayName": "Sample Desktop Tool",
"manufacturer": "Example Publisher",
"displayVersion": "1.0.0"
},
"sources": [
{
"alias": "appPayload"
}
],
"files": [
{
"id": "application",
"source": "appPayload",
"include": [
"**/*"
],
"exclude": [],
"targetRelativePath": "",
"required": true
}
],
"formats": {
"msix": {
"identity": {
"name": "ExamplePublisher.SampleDesktopTool",
"publisher": null,
"version": "1.0.0.0",
"architecture": "x64"
},
"minimumOsVersion": "10.0.19041.0",
"applications": [
{
"id": "App",
"executable": "SampleTool.exe",
"entryPoint": "Windows.FullTrustApplication",
"square44x44Logo": "Assets/Logo44.png",
"square150x150Logo": "Assets/Logo150.png"
}
],
"capabilities": [
"runFullTrust"
],
"extensions": [],
"dependencies": []
}
},
"targets": [
{
"id": "msix-direct",
"format": "msix",
"delivery": "package",
"distribution": "direct",
"signingProfileRef": null
}
]
}
@@ -0,0 +1,51 @@
# 1. 개발자 계정 만들기
[목차](README.md) · 다음: [앱 등록과 패키지 준비](02-app-and-package.md)
## 1. 계정 종류 선택
EG-BIM을 회사 제품으로 배포한다면 **Company(회사)**로 준비한다. 개인의 취미·학습 프로젝트라면 **Individual(개인)**을 검토한다. 회사가 소유할 앱을 직원 개인의 취미 계정으로 먼저 등록하지 않는 것을 권한다. 이는 이 프로젝트의 운영 권장사항이다.
Microsoft는 개인 계정을 회사 계정으로 전환하는 기능을 제공하지 않는다. 회사 명의 배포가 필요해지면 새 회사 계정을 만들어야 한다. [계정 유형과 가입 안내](https://learn.microsoft.com/en-us/windows/apps/publish/partner-center/open-a-developer-account)
## 2. 회사 담당자가 먼저 모을 자료
| 준비물 | 준비 방법 |
| --- | --- |
| 회사 정식 명칭·주소 | 회사 등록 서류의 표기를 확인한다. 앱 이름과 회사 법적 명칭은 다를 수 있다. |
| 사업체 확인 자료 | D-U-N-S 번호가 있으면 확인한다. 없으면 공식 사업자 등록·법인 등록 등 화면에서 허용하는 서류를 준비한다. |
| 담당자 이메일 | 인증 메일을 받을 실제 담당자의 회사 이메일을 준비한다. |
| 고객 지원 연락처 | 사용자에게 공개할 회사 연락처를 담당 부서와 정한다. |
| 로그인 담당자 | 계정 소유와 담당자 변경을 회사가 관리할 수 있도록 정한다. |
D-U-N-S는 회사를 식별하는 9자리 번호이며, 한국 사업자등록번호와 다른 값이다. 사업자등록번호를 D-U-N-S 입력칸에 넣지 않는다. 서류로 확인할 경우 최신성·유효기간 조건도 확인한다. 한국 서류의 허용 종류와 번역 요구 여부는 제출 화면/보완 요청을 따른다. [사업체 확인 자료 안내](https://learn.microsoft.com/en-us/windows/apps/publish/store-business-verification-reqs)
## 3. 회사 계정 가입 순서
1. 브라우저에서 [Microsoft Store 개발자 가입](https://storedeveloper.microsoft.com/)을 연다.
2. **Get started / Get started for free**를 누른다.
3. **Company account**를 선택한다.
4. Microsoft 계정 또는 지원되는 회사 Microsoft Entra ID 계정으로 로그인한다.
5. 회사 정보를 입력한다. D-U-N-S 조회 또는 공식 서류 제출 방식으로 확인한다.
6. 담당자·지원 연락처를 입력한다. **인증용 이메일은 회사 도메인과 일치하는 담당자 이메일**을 사용한다.
7. 약관을 확인하고, **Verification summary**에서 검증 진행 상황을 확인한다.
8. 추가 서류 요청이 있으면 해당 화면에서 보완한다. 검증 완료 후 **Publish to Store**로 이동한다.
새 가입 절차는 **개인·회사 모두 등록비 무료**다. 결제 화면이 나오면 시작 주소를 다시 확인한다. [가입 절차·비용](https://learn.microsoft.com/en-us/windows/apps/publish/partner-center/open-a-developer-account)
**완료 확인:** Partner Center에서 **Apps & Games(앱 및 게임)**를 열 수 있고, 가입 검증에 미완료 항목이 없어야 한다.
## 4. 개인 계정을 만드는 경우
**Individual developer** 선택 → 개인 Microsoft 계정 로그인 → 신분증·셀피 확인 → 프로필 작성 → **Go to Partner Center dashboard** 순서다. 개인 가입에는 회사/학교 계정을 사용하지 않는다. [개인 가입 안내](https://learn.microsoft.com/en-us/windows/apps/publish/partner-center/open-a-developer-account)
## 5. 로그인했는데 앱 메뉴가 없을 때
- 가입에 사용한 계정과 현재 로그인 계정이 같은지 먼저 확인한다.
- 회사에서 이미 가입했다면 새 계정을 만들기 전에 기존 관리자에게 앱 관리 권한을 확인한다.
- 회사 계정의 소유자 또는 관리자가 **Account settings → User management**에서 역할을 부여해야 할 수 있다. [역할 설정 안내](https://learn.microsoft.com/en-us/windows/apps/publish/partner-center/open-a-developer-account)
- 사업체 검증 문제는 **Account settings → Legal info → Developer**와 가입 검증 화면을 확인한다. 오류 내용을 기록하고 요청된 정보만 수정한다. [검증 상태 확인](https://learn.microsoft.com/en-us/windows/apps/publish/store-business-verification-reqs)
운영 메모: 로그인 주소, 담당 부서, 계정 종류, 검증 완료일을 회사 내부 관리 문서에 적는다. 비밀번호·신분증·사업체 증빙 원본은 소스 저장소에 넣지 않는다.
여기까지 끝나면 [2. 앱 등록과 패키지 준비](02-app-and-package.md)로 이동한다.
@@ -0,0 +1,102 @@
# 2. 앱 등록과 패키지 준비
[목차](README.md) · 이전: [개발자 계정](01-developer-account.md) · 다음: [제출과 공개](03-submit-and-update.md)
## 1. 앱 이름 예약하기
1. 가입을 완료한 계정으로 [Partner Center](https://partner.microsoft.com/dashboard)에 로그인한다.
2. **Apps & Games → New product**를 연다.
3. **MSIX or PWA app**을 선택한다. 이 매뉴얼은 MSIX 경로다.
4. 원하는 이름을 입력한다. 예: `EG-BIM`.
5. **Check availability**로 사용 가능 여부를 확인한다.
6. 사용 가능한 이름이면 **Reserve product name**을 누른다.
**완료 확인:** EG-BIM의 **Application overview(앱 개요)** 화면이 나온다. 이름 예약만으로 사용자에게 공개되지는 않는다. [Microsoft 시작 안내](https://learn.microsoft.com/en-us/windows/apps/publish/get-started)
## 2. Store가 정한 식별 정보 복사하기
앱을 연 상태에서 **Product management → Product identity**로 이동한다. 아래 세 값을 복사해 둔다. 예시를 임의로 만들어 입력하지 않는다.
| Partner Center 항목 | Installer Studio 입력 위치 |
| --- | --- |
| `Package/Identity/Name` | **패키지 설정 → Identity Name** |
| `Package/Identity/Publisher` | **패키지 설정 → Publisher** |
| `Package/Properties/PublisherDisplayName` | **앱 정보 → 게시자 표시명** |
`Publisher`는 `CN=...` 형태일 수 있으며 게시자 표시명과 다르다. **프로젝트 ID**에는 내부 관리용 `egbim` 등을 사용한다. Store ID나 MSI ProductCode를 프로젝트 ID에 넣을 필요는 없다. Store ID와 Package Family Name은 관리 기록으로 보관한다. [Store 식별 정보](https://learn.microsoft.com/en-us/windows/apps/publish/view-app-identity-details)
**완료 확인:** 세 값을 복사했으며 원문과 대소문자·공백까지 대조했다. 이 제작기는 자동 Store 연결 기능이 없으므로 사람이 옮겨야 한다.
## 3. Installer Studio에서 제출 후보 만들기
샘플 설정을 보존하려면 파일 탐색기에서 `InstallerProject.isproj`와 같은 폴더의 `InstallerProject.isproj.local.json`을 **별도 작업 폴더**로 복사한다. 이름이 다르면 해당 `.isproj`와 그 이름에 `.local.json`을 붙인 파일을 복사한다. `.work`와 `releases`는 복사할 필요 없다. 상대 소스 경로는 폴더가 바뀌면 달라지므로 아래에서 다시 연결한다. 기존 `.json` 프로젝트는 지원하지 않으므로 해당 샘플만 있다면 새 프로젝트를 만든다.
1. Installer Studio의 **열기**로 복사한 프로젝트를 연다.
2. **앱 정보**에서 예약한 앱 이름, 게시자 표시명을 입력한다.
3. **파일 구성**에서 실제 배포용 소스 폴더를 연결한다. EG-BIM 예시는 `D:\egbim\repo\14.1\Build`이며, 별도 구성 요소는 실제 위치를 확인한다.
4. **패키지 설정**에서 앞 단계의 Name·Publisher를 붙여넣는다.
5. 패키지 버전을 입력한다. 이 매뉴얼의 예시는 최초 `14.1.0.0`, 다음 업데이트 `14.1.1.0`이다. 실제 제품의 기존 배포 버전과 맞춰 결정한다.
6. 실행 파일과 아키텍처가 맞는지 확인한다. x64 앱에 단순히 arm64를 선택해서 변환할 수는 없다.
7. **레지스트리** 탭에서 필요한 설정을 확인한다. 이전 ISM의 설정이 모두 자동으로 가져와졌다고 가정하지 않는다.
8. **서명**에서 아래 용도 구분에 맞게 선택한다.
9. **검증·빌드**에서 SDK 도구 선택 → 입력 검사 → 빌드 계획 생성 → MSIX 빌드를 차례로 누른다.
10. 완료 화면의 산출물 경로를 기록하고 **결과·보고서 내보내기**로 보관한다. 기본 산출물은 프로젝트 폴더의 `releases\<빌드 ID>\package.msix`다.
Store 제출용 버전은 마지막 자리를 `0`으로 유지하는 규칙으로 운영한다. Name·Publisher를 유지하고 버전을 올린다. Microsoft의 Windows 10/11 패키지 버전 규칙과 파일 형식 조건을 함께 확인한다. [패키지 요구사항](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/app-package-requirements)
### 서명 선택은 어떻게 하나요?
| 목적 | 현재 제작기에서 할 일 |
| --- | --- |
| 내 PC/테스트 PC에서 먼저 설치 | **개발자 서명** 선택 → 현재 Publisher와 일치하는 인증서 생성/선택 → 빌드. 테스트 PC에는 공개 인증서를 별도로 신뢰 등록 |
| Store 업로드 후보 생성 | **미서명**으로 생성할 수 있다. Store 식별 정보를 정확히 넣고, 테스트용 빌드와 별도로 보관 |
| 일반 사용자가 웹에서 직접 설치 | 공인 서명 등 별도 배포 준비 필요. 현재 테스트 인증서를 그대로 일반 사용자 배포에 사용하지 않음 |
Store용 MSIX에는 유료 CA 인증서 구매가 선행 조건이 아니다. Microsoft가 심사 후 배포 과정에서 패키지를 다시 서명한다. 현재 미서명 파일을 더블클릭해 설치할 수 있다는 의미는 아니다. [서명 선택 안내](https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/code-signing-options)
**Store 제출물에 PFX, 인증서 개인 키, 테스트 인증서 등록 스크립트를 넣지 않는다.** 내부 테스트 인증서 등록 방법은 [개발자 서명 설명](../../changes/2026-10-23-developer-signing.md)을 따른다.
## 4. 현재 제작기의 Store 준비 상태
2026-10-26 소스 확인 기준이다. 아래는 Microsoft의 제한이 아니라 **우리 프로그램의 구현 상태**다.
| 항목 | 현재 상태와 해야 할 일 |
| --- | --- |
| MSIX 파일 생성 | 가능. 생성 결과를 Partner Center에서 수동 검사할 수 있음 |
| Name·Publisher·게시자 표시명 입력 | 가능. Store 값을 직접 입력 |
| Store 전용 출력 대상 | 미구현. JSON에서 `distribution`을 `store`로 바꾸면 현재 검증이 거부함 |
| 현재 `direct` 설정 | 일반 MSIX 생성 경로를 뜻함. Store 승인이나 자동 제출을 의미하지 않음 |
| 패키지 언어 | manifest 생성 코드가 `en-us`로 고정됨. 한국어 EG-BIM은 지원 언어 선언을 맞추는 개발 작업 후 다시 생성해야 함 |
| Store 전용 자산·버전·정책 검사 | 미구현. Windows App Certification Kit와 Partner Center에서 추가 확인 |
| 업로드·심사 요청·그룹 관리 | 브라우저에서 수동 진행 |
개발 담당자 확인 위치: [manifest 생성과 검증 코드](../../../src/crates/package-domain/src/lib.rs), [제작기 사용 설명](../../../src/README.md).
**한국어 앱을 그대로 제출하기 전에 언어 선언을 보완한다.** 이미 만든 MSIX 내부 파일을 직접 바꾸면 서명·검증 결과가 무효가 될 수 있으므로, 프로젝트/생성 코드를 수정한 후 새로 빌드한다. 이 매뉴얼 작성으로 해당 기능이 구현되지는 않았다.
## 5. 설치 및 실행 확인
EG-BIM 담당자는 테스트 PC에서 다음을 확인하고 결과를 기록한다.
- 처음 설치한 뒤 시작 메뉴에서 실행되는가?
- 기존 `C:\Program Files\Baron` 설치에 의존하지 않고 실행되는가?
- 사용자 설정·라이선스·문서가 읽히고 저장되는가?
- EgViewer, CAD 연동, 플러그인과 필요한 런타임이 실제로 동작하는가?
- 업데이트 후 사용자 데이터가 유지되는가?
- 기존 EXE 설치본과 함께 있을 때 충돌하거나 잘못된 프로그램이 실행되지 않는가?
- 제거 후 남겨야 하는 사용자 자료가 보존되는가?
이 목록은 EG-BIM 전환을 위한 프로젝트 점검 항목이다. 현재 샘플의 서명 성공만으로 완료 처리하지 않는다. 기존 EXE 설치본의 제거·데이터 이전도 자동 구현된 상태가 아니다.
Windows SDK의 **Windows App Certification Kit(WACK)**는 Store 관련 기술 검사를 수행하는 별도 도구다. 없다면 Windows SDK 설치 구성에서 해당 도구를 준비한다.
1. 위에서 만든 테스트 서명본을 테스트 PC에 설치한다.
2. 시작 메뉴에서 **Windows App Cert Kit**를 검색해 실행한다.
3. **Validate a Windows app**을 선택한다.
4. 설치한 EG-BIM을 선택한다. 목록에 없으면 **My app isn't listed**에서 전체 목록을 확인한다.
5. **Next**로 검사 항목을 확인하고 실행한다. 실패를 피하려고 필요한 검사를 임의로 제외하지 않는다.
6. 완료 시 보고서 저장 폴더를 지정한다. HTML/XML 결과를 열어 실패 항목을 확인한다.
실패 항목은 개발 담당자가 수정한다. 검사 통과 후에도 Store 심사는 별도로 진행된다. [WACK 사용 설명](https://learn.microsoft.com/en-us/windows/uwp/debug-test-perf/windows-app-certification-kit)
**완료 확인:** 설치·주요 기능·업데이트 시험 결과와 WACK 보고서가 준비되고, Store 식별 정보 및 언어 설정을 확인했다. 그다음 [3. 제출·심사·공개](03-submit-and-update.md)로 이동한다.
@@ -0,0 +1,111 @@
# 3. 제출·심사·공개·업데이트
[목차](README.md) · 이전: [패키지 준비](02-app-and-package.md) · 문제 해결: [오류와 체크리스트](04-troubleshooting.md)
## 1. 제출에 쓸 자료 준비
아래는 EG-BIM 담당자가 모아둘 작업 자료다. 사실과 다른 기능 설명이나 임시 문구는 제거한다.
| 자료 | 준비 예시 |
| --- | --- |
| 제출 후보 파일 | 검증한 `package.msix` |
| 앱 소개 | 누구를 위한 프로그램인지, 실제 제공하는 기능, 사용에 필요한 라이선스 |
| 실행 화면 | 시작 화면, 실제 작업 화면, 주요 결과 화면 |
| 고객 지원 | 담당 이메일, 지원 웹페이지 |
| 개인정보처리방침 | 개인정보 수집·전송이 있는 경우 실제 공개 URL |
| 심사 안내 | 실행 순서, 샘플 자료, 필요한 외부 프로그램, 테스트용 계정/라이선스 |
| 변경 사항 | 이번 버전에서 추가·수정한 내용 |
스크린샷은 현재 PC 앱을 직접 실행해 촬영한다. Desktop 기준 PNG, 1366×768 이상, 파일당 50MB 이하로 준비할 수 있다. 설치 프로그램 화면이나 제작기 화면이 아니라 **EG-BIM 사용 화면**을 올린다. 로고는 업로드 칸에 표시된 규격에 맞춘다. [공식 이미지 규격](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/screenshots-and-images)
## 2. 제출 초안 만들기
Partner Center에서 **Apps & Games → 해당 앱 → Application overview → Product release → Start submission**을 연다. 아래 항목을 작성하고 각각 **Save**를 누른다.
| 화면 | 입력 내용 |
| --- | --- |
| Pricing and availability | 가격, 제공 국가, 공개 대상 |
| Properties | 앱 분류, 회사 연락처, 필요한 개인정보처리방침 URL |
| Age ratings | 연령 등급 설문에 실제 앱 내용대로 답변 |
| Packages | MSIX 업로드 |
| Store listings | 지원 언어별 소개, 스크린샷 등 |
| Submission options | 심사 참고 사항, 공개 시점, 제한된 기능 사용 설명 |
회사 계정의 연락처, 개인정보 수집·전송 시의 개인정보처리방침 등 필수 항목을 확인한다. 앱 소개와 스크린샷은 최소 한 지원 언어에서 완성해야 한다. 최소 한 장의 스크린샷을 준비하고, 이 프로젝트에서는 기능을 보여주는 4장을 권한다. [제출 항목 안내](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/create-app-submission)
가격은 제품 판매 정책에 맞춰 결정한다. 기존 EG-BIM 라이선스가 필요한 앱이라면 다운로드 가격과 사용 라이선스 조건을 혼동하지 않도록 소개문에 설명한다. 유료 판매를 선택할 때는 계정에 요구되는 정산·세금 설정도 완료한다.
## 3. 첫 배포는 비공개 테스트로 시작
권장 절차는 **Pricing and availability → Visibility → Audience → Private audience**를 선택하고 테스트 그룹을 만드는 것이다. 대상자의 **개인 Microsoft 계정 이메일**을 등록한다. 회사 개발자 계정을 Entra ID로 만들었더라도, 비공개 앱을 받는 테스트 계정은 개인 Microsoft 계정이어야 한다.
**Public audience로 제출한 뒤에는 Private audience로 되돌릴 수 없다.** 최초 테스트 단계에서 공개 대상을 먼저 확인한다. 검색에서 숨기는 Discoverability 설정만으로 특정 사람만 설치하도록 제한되는 것은 아니다. [공개 범위 설정](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/visibility-options)
## 4. MSIX 업로드
1. **Packages**를 연다.
2. 파일 선택 또는 끌어놓기로 `package.msix`를 올린다. `.msix` 자체도 지원 형식이다.
3. 업로드와 자동 검사가 끝날 때까지 기다린다.
4. 오류가 있으면 해당 문구를 기록한다. 잘못된 파일을 제거하고 원본 설정을 수정해 다시 빌드한다.
5. 표시된 Name, 버전, 아키텍처, 지원 장치를 확인하고 **Save**한다.
Microsoft는 `.msixupload`도 권장하지만 현재 제작기는 `.msix`를 생성한다. 확장자만 `.msixupload`로 바꾸지 않는다. 로컬 프로젝트 JSON이나 CER는 패키지 업로드 대상이 아니다. [업로드 가능한 형식과 검사](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/upload-app-packages)
**완료 확인:** 패키지 검사 오류가 없고 제출 필수 항목이 완료됐다. 파일에 `Validated`가 보여도 섹션 전체는 필수 입력 누락으로 `Incomplete`일 수 있다. [제출 완료 조건](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/create-app-submission)
## 5. 심사자가 실행할 수 있도록 설명
**Submission options → Notes for certification**에 아래 항목을 실제 동작에 맞춰 작성한다. 이 틀은 EG-BIM용 작성 예시이며 완성된 심사 설명문은 아니다.
```text
앱 목적: [실제 EG-BIM 기능 설명]
실행 방법: 시작 메뉴에서 [실제 이름] 실행
시험 순서: [샘플 열기 → 기능 실행 → 결과 확인]
필요 환경: [지원 OS, 외부 CAD/런타임 등 실제 필요한 항목]
라이선스/로그인: [필요 여부, 심사용 접근 방법]
runFullTrust 사용 이유: [기존 Win32 실행 구조와 필요한 기능을 구체적으로 설명]
이번 변경: [수정한 내용]
```
현재 제작기는 `runFullTrust`를 선언한다. 제한된 기능 설명을 요청받으면 앱에서 왜 필요한지 작성한다. 이것이 관리자 권한의 자동 부여를 의미하지는 않는다. 시험에 로그인이 필요하면 운영 계정 대신 심사용 계정을 마련하고 접근 방법을 심사 메모에 제공한다. [심사 메모와 기능 설명](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/manage-submission-options)
공개 시점을 직접 정하려면 같은 화면의 Publishing hold 옵션에서 **Publish now를 선택할 때까지 공개하지 않는 항목**을 선택한다. 기본값은 심사 통과 후 공개 진행이므로 최초 제출에서 확인한다. [공개 보류 옵션](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/manage-submission-options)
## 6. 심사 요청 및 결과 확인
1. 앱 개요에서 누락 항목을 확인한다.
2. **Submit for certification**을 누른다.
3. 상태와 알림 이메일을 확인한다. 심사에는 보안·기술·내용 검토가 포함된다.
4. 거절되면 심사 보고서의 실패 항목을 수정하고 다시 제출한다.
5. 통과 후 수동 공개 보류를 선택했다면 **Publish now**로 배포를 시작한다.
통과·공개까지 걸리는 시간은 심사 상태에 따라 달라진다. 공개 처리 중과 실제 Store 설치 가능 상태를 구분한다. [심사 절차](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/app-certification-process)
비공개 앱은 Partner Center가 제공하는 **인증이 필요한 비공개 앱 링크**로 테스트한다. 대상 Microsoft 계정으로 Store에 로그인해 설치·실행을 확인한다. 비대상 계정의 접근 제한도 확인한다. [비공개 링크 안내](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/visibility-options)
## 7. 개발자 → 1차 → 2차 → 일반 사용자
최초 제출을 게시한 후 앱 개요의 **Package flights → New package flight**에서 테스트 배포를 만든다. Flight는 특정 그룹에 다른 패키지를 제공하는 기능이다. 테스트 패키지도 심사를 거친다. [Package flights](https://learn.microsoft.com/en-us/windows/apps/publish/package-flights)
| 단계 | 이 프로젝트의 권장 그룹 | 진행 기준 |
| --- | --- | --- |
| 개발자 | `egbim-dev` | 설치·실행·기본 기능 확인 |
| 1차 테스터 | `egbim-test1` | 주요 업무 시나리오 확인 |
| 2차 테스터 | `egbim-test2` | 실제 작업 환경·업데이트 확인 |
| 사용자 | 일반 제출 | 앞 단계 결과를 확인하고 일반 공개 |
Flight 생성 시 이름과 테스트 그룹을 지정하고, Packages에 패키지를 추가해 제출한다. 다음 단계에는 검증한 패키지를 복사/선택해 같은 후보를 배포할 수 있다. 여러 Flight에 속한 사용자는 설정한 우선순위의 영향을 받으므로 처음에는 그룹이 겹치지 않게 운영한다. 비공개 앱이라면 상위 Private audience에도 해당 테스터가 포함되어야 한다. [Flight 생성·순위](https://learn.microsoft.com/en-us/windows/apps/publish/package-flights), [비공개 대상과 Flight](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/visibility-options)
일반 공개할 때는 새 일반 제출에서 검증 패키지를 선택하고 Public audience로 전환한다. **현재 Installer Studio에 이 네 단계 관리 화면은 없으며 Partner Center에서 운영한다.** Cloudflare의 그룹 배포 설정과 자동 연동되지 않는다.
## 8. 다음 버전 배포
이 프로젝트의 반복 작업은 다음과 같다.
1. 같은 Store 앱의 Name·Publisher를 유지한다.
2. 예를 들어 `14.1.0.0`을 `14.1.1.0`으로 올리고 전체 MSIX를 새로 만든다.
3. 기존 버전에서 업데이트하는 시험을 진행한다.
4. Flight에서 확인한 뒤 같은 앱의 업데이트 제출로 배포한다. 새 제품을 만들지 않는다.
5. 새 설치와 기존 설치의 업데이트 결과를 각각 확인한다.
문제 버전을 받은 사용자에게 낮은 버전이 자동 설치된다고 가정하지 않는다. 수정 패키지에 더 높은 버전을 부여해 재제출하는 복구 계획을 준비한다. [버전 및 복구 안내](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/app-package-requirements)
@@ -0,0 +1,65 @@
# 4. 문제가 생겼을 때와 최종 체크리스트
[목차](README.md)
## 오류를 어디서 해결하나요?
아래 표는 앞 문서의 확인 순서를 묶은 작업표다. 오류 전문을 보관한 뒤 해당 단계로 돌아간다.
| 상황 | 먼저 할 일 | 설명 위치 |
| --- | --- | --- |
| 가입 중 결제 화면이 나옴 | 새 가입 시작 주소와 최신 등록 경로 확인 | [계정 만들기](01-developer-account.md) |
| 회사 계정 검증이 끝나지 않음 | 검증 화면의 추가 서류·이메일 요청 확인 | [계정 만들기](01-developer-account.md) |
| Apps & Games가 안 보임 | 로그인 계정과 회사 역할 확인 | [계정 만들기](01-developer-account.md) |
| 앱 이름을 예약할 수 없음 | 사용 가능 여부 확인 후 다른 이름 검토 | [앱 이름 예약](02-app-and-package.md) |
| Identity/Publisher 불일치 | Product identity에서 다시 복사해 새 빌드 | [식별 정보 입력](02-app-and-package.md) |
| 로컬 설치에서 인증서 신뢰 오류 | 테스트용 서명 및 해당 PC의 공개 인증서 등록 확인 | [개발자 서명](../../changes/2026-10-23-developer-signing.md) |
| 제작기에서 Store 대상 미지원 | 현재 전용 대상 미구현. JSON 값을 바꿔 해결하지 않음 | [제작기 범위](02-app-and-package.md#4-현재-제작기의-store-준비-상태) |
| 한국어 앱인데 en-us로 표시됨 | manifest 언어 설정 보완 후 재생성 | [제작기 범위](02-app-and-package.md#4-현재-제작기의-store-준비-상태) |
| 업로드 후 Incomplete | 패키지 오류와 섹션 필수 항목을 모두 확인 | [패키지 업로드](03-submit-and-update.md) |
| 테스트 그룹인데 설치할 수 없음 | 개인 Microsoft 계정 이메일, Private audience, 링크 확인 | [비공개 테스트](03-submit-and-update.md) |
| 설치는 되지만 EG-BIM 실행 실패 | 런타임, 경로, 라이선스, 레지스트리, 외부 연동 시험 | [설치 및 실행 확인](02-app-and-package.md) |
| 심사자가 앱을 시험하지 못함 | 심사 보고서와 Notes for certification 보완 | [심사 요청](03-submit-and-update.md) |
계정 문제를 문의할 때는 계정 종류, 발생 단계, 오류 문구, 발생 시간을 준비한다. Microsoft의 지원 창구는 [Store 시작 FAQ](https://learn.microsoft.com/en-us/windows/apps/publish/faq/get-started-with-the-microsoft-store)를 참고한다. 개인 인증자료나 계정 비밀번호는 이 저장소에 첨부하지 않는다.
## 제출 담당자 체크리스트
이 목록은 EG-BIM의 작업 확인용이다. 모든 항목을 체크해도 Microsoft의 승인 자체를 보장하는 것은 아니다.
- [ ] 올바른 회사/개인 계정으로 가입했고 검증을 완료했다.
- [ ] 담당자 변경 시 계정을 관리할 회사 내부 절차가 있다.
- [ ] 앱 이름을 예약하고 세 가지 Store 식별 정보를 기록했다.
- [ ] 패키지의 Name·Publisher·게시자 표시명이 Store 값과 같다.
- [ ] 샘플 이름·임시 로고·임시 소개를 실제 제품 정보로 교체했다.
- [ ] 실제 지원 언어와 manifest 언어 선언을 맞췄다.
- [ ] 신규 설치·주요 기능·기존 버전 업데이트를 시험했다.
- [ ] 기존 EXE 설치본과 사용자 데이터 처리 방식을 확인했다.
- [ ] WACK 보고서와 빌드 결과를 보관했다.
- [ ] 패키지와 소개문이 같은 제품·버전을 설명한다.
- [ ] 개인정보처리방침, 지원 연락처, 라이선스 조건을 확인했다.
- [ ] 심사자가 기능을 실행할 수 있는 안내를 준비했다.
- [ ] 최초 공개 대상과 공개 시점을 확인했다.
- [ ] Store에서 실제 설치·업데이트 결과를 확인할 담당자를 정했다.
## 배포 기록 양식
아래 내용을 회사 내부 배포 기록에 복사한다. 실제 계정 정보나 승인 여부를 대신 작성한 것은 아니다.
```text
제품명:
담당자:
Store ID:
Identity Name:
Publisher:
PublisherDisplayName:
패키지 버전 / 아키텍처:
제작기 빌드 ID / 산출물 경로 / SHA-256:
설치·업데이트 시험 기록:
WACK 보고서 위치:
제출 날짜 / 제출 번호:
대상 그룹 / 공개 범위:
심사 결과 / 보완 사항:
실제 공개 날짜:
Store 설치·업데이트 확인 결과:
```
+46
View File
@@ -0,0 +1,46 @@
# 처음 시작하는 Microsoft Store 배포
대상: EG-BIM 같은 Windows 데스크톱 프로그램을 **MSIX 파일로 Microsoft Store에 올리려는 담당자**.
확인일: **2026-10-26, 한국 시간**. 웹 화면은 계정·언어·개편에 따라 달라질 수 있어 영문 메뉴명도 함께 적었다. 실제 계정 생성이나 Store 제출을 수행한 기록이 아니라, Microsoft 공식 안내와 현재 Installer Studio 코드를 확인해 작성한 매뉴얼이다.
## 어디부터 시작하나요?
아래 문서를 순서대로 따라간다. 처음에는 1번의 계정 준비와 2번의 앱 이름 예약까지만 해도 된다. 프로그램이 완성되기 전에 준비할 수 있다.
| 순서 | 따라 할 문서 | 끝나면 준비되는 것 |
| --- | --- | --- |
| 1 | [개발자 계정 만들기](01-developer-account.md) | 회사 또는 개인 개발자 계정 |
| 2 | [앱 등록과 패키지 준비](02-app-and-package.md) | Store 식별 정보, 테스트 및 제출 후보 MSIX |
| 3 | [제출·심사·공개·업데이트](03-submit-and-update.md) | 비공개 테스트부터 일반 사용자 공개까지의 절차 |
| 4 | [문제가 생겼을 때와 최종 체크리스트](04-troubleshooting.md) | 오류별 확인 위치, 제출 전 점검표 |
전체 흐름:
```text
개발자 계정 만들기 → 앱 이름 예약 → Store 식별 정보 복사
→ Installer Studio 설정 → 패키지 생성·설치 시험
→ Partner Center에 업로드 → 심사 → 테스트 그룹 배포 → 일반 공개
```
## 이름이 비슷해서 헷갈리는 것
| 용어 | 쉬운 설명 |
| --- | --- |
| Microsoft 계정 | Microsoft 서비스에 로그인하는 계정 |
| 개발자 계정 | Store에 앱을 올릴 수 있도록 등록·검증된 게시자 계정 |
| Partner Center | 앱 등록, 제출, 심사 결과를 관리하는 웹사이트 |
| Installer Studio | 이 저장소에서 개발 중인 설치 패키지 제작 프로그램 |
| MSIX | 배포할 프로그램 파일과 설치 정보를 담은 패키지 |
| 제출(Submission) | 패키지와 소개 자료를 묶어 심사를 요청하는 한 차례 작업 |
| 인증서 | 패키지 서명에 사용하는 파일/저장소 정보. 개발자 계정과 별개 |
Store의 MSIX 배포에서는 Microsoft가 패키지 호스팅과 배포 서명을 처리한다. 이 경로를 시작하기 위해 Cloudflare 업로드나 유료 코드 서명 인증서 구매를 먼저 할 필요는 없다. 자체 웹사이트 배포는 별도 경로다. [배포 방식 안내](https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/choose-distribution-path)
## 현재 프로그램으로 할 수 있는 범위
Installer Studio는 파일·레지스트리 설정, Identity 입력, MSIX 생성, 테스트용 자체 서명을 지원한다. **Store 계정 연결, 자동 업로드, Store 전용 사전 검사는 아직 없다.** 이 매뉴얼의 웹 작업은 브라우저의 Partner Center에서 직접 한다.
현재 코드에는 Store 제출 전에 보완할 부분도 있다. 특히 패키지 언어가 `en-us`로 고정되어 있으며 한국어 앱에 맞춘 설정이 필요하다. [2번 문서의 현재 구현 확인](02-app-and-package.md#4-현재-제작기의-store-준비-상태)을 먼저 읽는다. 패키지 생성 성공을 Store 심사 통과로 간주하지 않는다.
관련 자료: [기존 Store 배포 설계](../../plan/05-microsoft-store.md), [개발자 서명 사용 방법](../../changes/2026-10-23-developer-signing.md).
@@ -1,5 +1,9 @@
# 요구사항과 기존 프로그램 현황
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
제품의 공통 요구는 여러 제품의 EXE·MSIX 설치 패키지를 만드는 범용 제작기다. 아래 경로·GUID·수량·기존 업데이터 분석은 **EG-BIM 적용 사례**의 현황이며 공통 엔진 기본값이나 모든 제품의 선행 조건이 아니다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md) 참조.
EG-BIM의 기존 설치 기반을 보존하면서 네트워크 배포를 추가한다. 설치 도구 변경과 앱 구조 변경을 한 번에 강제하지 않는 방향으로 검토한다.
## 확정 요구사항
+4
View File
@@ -1,5 +1,9 @@
# InstallShield 대체와 기존 설치본 전환
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
이 문서는 EG-BIM의 InstallShield→EXE/MSI 전환 적용 계획이다. 범용 제작기는 별도로 MSIX 출력도 필수 지원한다. 다른 제품의 패키지 생성에 EG-BIM 자료나 구버전 제거를 요구하지 않는다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md) 참조.
권고안은 WiX로 MSI를 작성하고 Burn으로 필수 런타임과 MSI를 묶은 전체 설치 EXE를 만드는 것이다. 사용자에게는 기존처럼 EXE 설치 파일을 제공할 수 있다. WiX 채택과 도구 버전·이용 조건은 구현 시작 전에 확정한다.
## 대체 도구 검토
+4
View File
@@ -1,5 +1,9 @@
# 버전별 패키지 관리와 업로드
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
불변 아티팩트·해시·증빙 원칙은 EXE와 MSIX에 공통 적용한다. MSI/MSP 식별자와 기준 상태는 EXE 대상에만 적용하고 MSIX identity/업데이트 경로는 별도 기록한다. 아래 서버 업로드는 선택한 배포 연계의 책임이며 로컬 빌드의 필수 의존성이 아니다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md) 참조.
패키지는 등록 후 변경하지 않고 배포 정책만 변경한다. 같은 릴리스를 개발자 테스트부터 일반 배포까지 사용하며, 수정이 필요하면 새 릴리스를 만든다.
## 버전 및 관리 정보
+4
View File
@@ -1,5 +1,9 @@
# 사용자 그룹별 4단계 배포
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
이 문서는 자체 서버와 EG-BIM 적용 트랙의 단계 배포 계약이다. 범용 EXE/MSIX 생성은 이 서버 없이 가능해야 한다. Store 공개·MSIX 업데이트는 각 배포 대상의 별도 정책을 사용하며 서버 승인 상태와 동일시하지 않는다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md) 참조.
하나의 릴리스를 네 그룹에 순차적으로 공개한다. 초기에는 담당자가 관측 결과를 확인한 후 다음 단계를 승인하는 방식을 권고한다.
## 그룹 구성
+23 -6
View File
@@ -1,6 +1,10 @@
# Microsoft Store 배포 방식
현재 EG-BIM에는 자체 업데이터와 PC 전체 설치 구조가 있으므로 MSI·EXE 등록 경로를 우선 검토한다. Store 배포에 MSIX가 반드시 필요한 것은 아니다.
처음 등록하는 담당자는 [초보자용 Store 배포 매뉴얼](../manuals/microsoft-store/README.md)을 따른다. 2026-10-26 확인한 계정 가입 절차와 현재 제작기의 지원 범위를 별도로 설명한다. 이 문서의 출력 계약은 설계이며 현재 구현 완료 목록이 아니다.
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
**Store는 범용 제작기의 출력 파일을 등록하는 배포 채널이다.** 제작기는 Store 등록 여부와 관계없이 EXE와 MSIX를 모두 생성한다. 제품마다 적합한 형식으로 등록하며 EG-BIM의 기존 구조만으로 범용 제품의 형식을 제한하지 않는다. [출력과 배포 대상 계약](../code-design/11-generic-exe-msix-architecture.md) 참조.
## 두 경로의 차이
@@ -11,7 +15,7 @@
| 업데이트 | 자체 업데이터 담당 | Store 관리 |
| 지정 테스터 배포 | 자체 서버에서 구현 | Package Flights 지원 |
| 비율 배포 | 자체 서버에서 구현 | Store 점진 업데이트 지원 |
| 현재 앱 적용 | 기존 경로와 설치 기능을 유지하는 방향 | 경로·레지스트리·CAD 연동·업데이터 검증 필요 |
| 제품별 적합성 | 기존 설치 경로와 무인 실행 동작 검증 | 패키지 identity·선언 기능·앱 동작 검증; CAD 연동은 해당 제품만 추가 |
[Microsoft 배포 방식 비교](https://learn.microsoft.com/en-us/windows/apps/distribute-through-store/how-to-distribute-your-win32-app-through-microsoft-store)
@@ -25,9 +29,9 @@
Store용은 필요한 오프라인 런타임과 MSI를 포함한 단일 `FullSetup.exe` 등 제출 요건을 만족하는 파일로 만든다. 대용량으로 단일 파일의 생성·서명 제한을 넘으면 이 경로를 사용 가능하다고 간주하지 않는다. 자체 배포용 분할 CAB 폴더나 작은 `WebSetup.exe`가 그대로 Store 제출을 대신하는 것은 아니다. 현재 `.ism`의 웹 설치형 선행 조건은 그대로 이관하지 않고 실제 의존성을 정리한다. [공식 패키지 요구사항](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msi/app-package-requirements), [출력 프로필과 용량 검사](../code-design/05-release-pipeline.md)
## 자체 배포와 함께 운영
## EXE 자체 배포와 함께 운영
자체 사이트와 Store에서 같은 검증 완료 전체 설치 파일을 사용할 수 있다. Store에는 일반 사용자에게 공개할 릴리스만 등록한다. 최초 설치 후의 업데이트는 자체 서버가 그룹별로 제어한다.
자체 사이트와 Store에서 같은 검증 완료 전체 설치 파일을 사용할 수 있다. EG-BIM 적용 정책에서는 Store에 일반 사용자에게 공개할 릴리스만 등록한다. 다른 제품의 공개 정책은 해당 프로젝트에서 정한다. 최초 설치 후의 업데이트는 자체 서버가 그룹별로 제어한다.
Store MSI·EXE 등록은 자체 서버의 4단계 배포 정책을 대신하지 않는다. 자체 서버의 배포 중지와 Store의 새 사용자 설치 중지는 별도 조치다. 두 경로의 공개 상태와 설치 패키지 버전을 함께 기록해야 한다.
@@ -35,16 +39,29 @@ Store 등록·심사 시점과 자체 배포 시점을 조정하고, Store의
Store의 신규 설치용 등록 파일은 항상 전체 MSI·EXE로 유지한다. MSP를 신규 설치 파일 대신 제출하지 않는다. 설치 이후 자체 업데이터는 [버전별 패치 정책](08-version-patching.md)에 따라 호환되는 MSP를 적용할 수 있으며, 해당 버전의 전체 설치본과 패치 적용 결과가 같은 관리 상태가 되는지 검증한다.
## MSIX로 전환할 경우
## 제작한 MSIX를 Store에 제출할 경우
Package Flights에 개발자·1차·2차 테스트 그룹을 구성하고 정식 제출을 일반 사용자 단계에 연결할 수 있다. Microsoft 계정으로 테스터를 등록하고 Flight도 인증 절차를 거친다. 초기 앱 제출 게시 후 Flight를 구성하는 흐름을 고려한다. [Package Flights](https://learn.microsoft.com/en-us/windows/apps/publish/package-flights)
Store 점진 배포는 기존 사용자의 업데이트에 대한 비율 제어다. 최초 설치에도 같은 비율 제한이 적용된다고 가정하지 않는다. 중지는 이미 업데이트된 앱을 이전 버전으로 되돌리지 않으므로 수정 릴리스를 준비해야 한다. [점진 배포](https://learn.microsoft.com/en-us/windows/apps/publish/gradual-package-rollout)
MSIX 전환 시에는 고정 설치 경로, 설치 위치 쓰기, 외부 CAD·플러그인·COM 연동, 사용자 설정 이동, 런타임을 검증한다. 현재 구조만으로 불가능하다고 판정한 것은 아니다. [MSIX 준비 사항](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-prepare)
기존 제품을 MSIX로 배포하려면 설치 위치 쓰기, 외부 연동, 데이터 이전, 런타임을 제품별로 검증한다. EG-BIM의 CAD·플러그인·COM 적합성은 별도 검토다. 범용 MSIX 출력 기능을 이 제품의 전환 성공에 종속시키지 않는다. [MSIX 준비 사항](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-prepare)
## SmartScreen과 설치 경로
Microsoft 공식 안내는 Store를 통한 MSI·EXE 설치 과정에는 SmartScreen 경고가 없고 UAC는 나타날 수 있다고 구분한다. 이는 Microsoft가 해당 EXE를 재서명한다는 의미가 아니다. Store에서 제공하는 MSIX 패키지 서명 처리와 다르다.
같은 EXE를 자체 웹사이트에서 내려받으면 일반 다운로드의 SmartScreen 평판 판단을 받는다. Store 등록만으로 자체 배포 URL까지 경고 면제가 되지는 않는다. Store 설치 후 자체 업데이터가 내려받는 파일도 별도의 서명·검증이 필요하며 Store 설치 경로의 효과를 그대로 가정하지 않는다. [코드 서명 및 배포 경로 비교](https://learn.microsoft.com/en-us/windows/apps/package-and-deploy/code-signing-options)
## 등록 전 제작기의 산출물
| 출력 대상 | 생성/검증 | 등록 단계 |
| --- | --- | --- |
| 직접 배포 EXE | 단일/분할/네트워크 구성과 자체 서명 | 자체 배포; Store 불필요 |
| Store EXE | 완전 오프라인 단일 파일, 서명, 무인 설치 인자와 제출 검사 보고서 | 고정 HTTPS URL과 등록 자료를 별도로 제출 |
| 직접 배포 MSIX | manifest·MakeAppx 패키지·신뢰 가능한 서명; 선택적으로 App Installer | 직접 설치/기업 배포; Store 불필요 |
| Store MSIX | Store identity를 적용한 `.msix`, 제출 검사 보고서 | Partner Center 업로드, 심사 후 Store 배포 서명 |
MSIX 자체 생성은 1차 필수다. Store 제출용 내보내기 계약과 등록은 후속 범위다. 실제 계정 설정·등록·심사·공개 운영은 BL-44/45의 선택 작업이다. Store 제출 파일은 직접 설치용 서명본과 별도 아티팩트로 관리한다. [MSIX 업로드 지원 형식](https://learn.microsoft.com/en-us/windows/apps/publish/publish-your-app/msix/upload-app-packages)
설치 경로와 UI는 배포 채널이 아니라 형식의 제약을 따른다. EXE는 제품 프로젝트가 정한 경로/기본값과 무인 인자를 사용한다. MSIX는 Windows가 경로를 관리하므로 제작기에 임의 설치 폴더나 Burn 마법사 편집을 제공하지 않는다. 앱 데이터 경로는 패키지 설치 위치와 분리한다. [MSIX 파일 배치](https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-behind-the-scenes)
@@ -1,5 +1,9 @@
# 코드 서명 서비스와 SmartScreen
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
EXE 내부 PE·MSI·MSP·Burn의 서명 순서와 MSIX 패키지 서명을 구분한다. 직접 배포 MSIX는 manifest Publisher와 인증서 Subject 일치 및 대상 PC의 신뢰를 검사한다. Store MSIX 제출 파일과 Store가 최종 서명한 배포 파일의 상태·해시는 별도이며, 제출용 미서명 파일을 직접 배포 준비 완료로 표시하지 않는다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md) 참조.
WiX는 설치 패키지 생성 도구다. 회사 명의의 코드 서명 인증서 또는 서명 서비스는 별도로 준비해야 한다. WiX 빌드에서 서명 도구를 호출해 자동화할 수 있다.
## 역할과 서비스 선택
@@ -1,7 +1,13 @@
# 구현 순서와 검증 계획
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
현재 제품 완료 기준에는 **EXE와 MSIX 생성·검증**이 모두 포함된다. 아래 legacy 전환·자체 서버·4단계 배포는 EG-BIM 적용·연계 트랙이다. 범용 제작기는 두 독립 가상 제품에서 두 형식의 설치/업데이트/제거를 검증하며 EG-BIM 전환·Store 심사를 공통 생성 차단 조건으로 두지 않는다. 통합 순서는 [일정 v2](10-development-schedule.md)와 BL-46~53을 포함한 백로그를 따른다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md) 참조.
첫 검증 목표는 기존 InstallShield 설치본을 새 설치본으로 전환하면서 사용자 설정과 CAD 연동을 보존하는 것이다. 이 경로를 검증한 뒤 서버의 단계 배포를 연결한다.
영역별 구현 항목은 아래 목록을 유지한다. 2026-10-19 기준 통합 실행 순서와 인력·일정은 [개발 일정과 마일스톤](10-development-schedule.md), 개별 작업의 선행 조건과 완료 기준은 [구현 백로그](../backlog/README.md)를 따른다.
## 구현 전 결정할 항목
| 항목 | 현재 상태 | 필요한 결정·확인 |
+4
View File
@@ -1,5 +1,9 @@
# 버전별 패치와 복구
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
이 문서의 MSP·MSI 기준 상태·Burn 적용 경로는 **EXE/MSI 제품 전용**이다. 범용 제작기의 MSIX 출력은 버전 증가 패키지로 업데이트하며 MSP를 적용하지 않는다. EXE↔MSIX 전환은 별도 데이터 이전·공존 검증이 필요한 작업이다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md) 참조.
버전별 패치는 필수 요구사항이다. 현재 설치된 버전과 상태를 확인해 변경분만 적용할 수 있어야 하며, 신규 설치·최초 설치기 전환·패치 비호환 상태를 위한 전체 설치 패키지도 함께 제공한다.
## 권장 구현 방식
+41
View File
@@ -0,0 +1,41 @@
# 계획 검토 결과
현재 1차 범위는 [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)다. EXE/MSP·EG-BIM 전환·자체 그룹 배포·Store 제출은 후속으로 유지하며 첫 출시 의존성에서 제외한다. 아래 기존 분석/확장 계약보다 이 범위와 [일정 v3](10-development-schedule.md)가 우선한다.
범위 정정: 초기 검토의 EG-BIM 중심 가정과 MSIX 후순위 분류를 수정했다. [범용 출력 설계](../code-design/11-generic-exe-msix-architecture.md)와 [변경 기록](../changes/2026-10-19-general-package-builder.md)이 현재 기준이다. 기존 위험 분석은 해당 EXE/EG-BIM 적용 범위에서 유효하다.
초기 검토일: 2026-10-19, Asia/Seoul. 초기 검토 대상은 `docs/plan/01~08`, `docs/code-design/01~09`, 프로젝트 JSON 예제와 설치 화면 시안이다. 이번 검토는 문서의 범위·의존성·완료 기준을 대조한 것이다. 기존 EG-BIM 소스나 실제 출시 설치 파일을 다시 조사하거나 Windows 설치 시험을 실행한 결과는 아니다. 외부 서비스 가격·가입 자격·Store 정책도 초기 검토에서는 재확인하지 않았다. 이후 범용 범위 정정에서는 MakeAppx·MSIX 서명/버전·Store 제출 공식 문서를 확인하고 11번 설계에 근거를 연결했다. 서비스 가격·계약 자격은 재검토 범위가 아니다.
## 판단
범용 EXE·MSIX 제작을 중심으로 형식별 업데이트와 사용자 데이터 보존·불변 아티팩트를 유지한다. 먼저 두 형식의 작은 빌드/설치 실험과 공통 모델을 검증한다. EG-BIM 적용에는 실제 구버전 설치본 확보와 전환·패치·설치 UI 실험을 추가한다.
현재 저장소에는 계획·설계·시안이 있으며 실행 가능한 Rust/Tauri 제작기, WiX 패키지, 업데이터 연동, 서버 구현은 없다. 설계에 적혀 있는 기능을 구현 완료로 간주하지 않는다. 아래 백로그의 구현 항목은 모두 미착수로 시작한다.
## 주요 발견과 조치
| ID | 중요도 | 근거와 발견 | 영향 및 권고 조치 | 연결 백로그 |
| --- | --- | --- | --- | --- |
| R01 | 높음 | [운영 구현 순서](07-implementation-and-validation.md)는 전환 보존을 첫 목표로 두지만 [코드 구현 순서](../code-design/08-validation-and-roadmap.md)는 실제 EG-BIM 전환을 UI 뒤에 배치한다. | 순서가 다른 두 목록은 제품 전체의 일정을 제공하지 못한다. 실제 구버전 제거 영향 실험을 초반에 하고, 본 전환 구현을 패치·GUI 완성보다 앞에 둔다. | BL-01, BL-07, BL-22 |
| R02 | 높음 | [설치 UI 설계](../code-design/09-install-flow-and-ui.md)는 Rust/Burn 연결과 WebView2 없는 PC의 시작 경로를 미검증으로 명시한다. | 자유로운 페이지 편집을 구현한 뒤 실행 백엔드가 불가능하다고 판명될 수 있다. 최소 BA 연결 실험을 초기에 실시하고 실패 시 대안·유지보수 비용을 기록한다. 기본 테마만으로 자유 페이지 요구를 완료 처리하지 않는다. | BL-08, BL-31, BL-32 |
| R03 | 높음 | [전체 릴리스](../code-design/05-release-pipeline.md)와 [패치 설계](../code-design/06-patch-pipeline.md)는 같은 ProductCode의 전체 대체 경로와 Burn/MSP 유지보수를 미검증으로 남긴다. | MSP 생성 성공만으로 배포할 수 없다. 실제 source state별 전체 대체 경로와 repair·제거·다음 업데이트를 패치 완료의 필수 조건으로 묶는다. | BL-04, BL-25~28 |
| R04 | 높음 | [프로젝트 예제](../code-design/examples/egbim.package-project.json)의 제조사·서명 프로필은 비어 있고 런타임·레지스트리·전환 검증 플래그는 미확정이다. | 실제 payload와 출시본 목록이 없으면 일정과 설치 결과를 검증할 수 없다. 확인 자료를 별도 관리하고 초안 프로젝트의 정식 릴리스 확정을 차단한다. | BL-01~03, BL-10 |
| R05 | 높음 | [생성기 상태](../code-design/05-release-pipeline.md)의 `Finalized/DeploymentReady`와 [서버 상태](03-package-and-upload.md)의 `Uploaded/Verified/Ready`가 각각 존재한다. | 서로 다른 검증 범위를 같은 성공으로 해석할 위험이 있다. 상태 간 매핑, 증빙 발급 주체, 누락·만료·철회 처리를 하나의 내보내기/수신 계약으로 정의한다. | BL-20, BL-34, BL-35 |
| R06 | 높음 | [기존 업데이트 현황](01-requirements-and-current-state.md)은 NuGet 호출부만 확인했고, 사용자 채널과 장치 단위 배포 그룹의 대응도 미정이다. | 기존 라이브러리 재사용을 전제로 일정 단축을 계산하지 않는다. 실행·권한·종료 코드·재부팅·서명 검증 지원을 조사하고 부족한 부분의 구현 경계를 확정한다. | BL-05, BL-23, BL-24, BL-36~39 |
| R07 | 중간 | [배포 승인 기준](04-staged-rollout.md)의 관찰 기간·성공률·표본 수는 예시이고 승인 담당자는 미정이다. | 날짜 도달을 승격 승인으로 사용할 수 없다. 분모·취소·미응답·재부팅 대기 처리와 중대 장애 기준을 정하고 실제 테스터 확보를 출시 의존성으로 둔다. | BL-06, BL-38, BL-42, BL-43 |
| R08 | 중간 | [서명](06-code-signing-and-smartscreen.md), [Store](05-microsoft-store.md)는 계약·제3자 파일·용량·외부 정책에 의존한다. | 서명 준비는 초반에 시작하되 외부 처리 기간을 개발 완료로 보장하지 않는다. Store는 별도 가능성 판단 후 일정에 추가한다. 단일 파일 출력 불가를 전체 설치 기능 실패와 구분한다. | BL-03, BL-18, BL-19, BL-44, BL-45 |
| R09 | 중간 | [시안](../../output/egbim-installer-preview.html)은 설치 사용자의 화면이며 [제작기 화면 목록](../code-design/07-ui-and-commands.md)을 구현한 것이 아니다. | 제작기와 최종 Setup 화면을 별도 항목으로 추적한다. 시안의 성공 상태와 진행률은 실제 측정 결과가 아니며 native 화면·접근성·실패 상태 시험이 남아 있다. | BL-29~33 |
| R10 | 중간 | [보존 정책](03-package-and-upload.md)은 지원 기준·패치·복구·Store URL 보존을 요구하지만 기간과 삭제 절차는 미정이다. | 오래된 원본을 정리하면 repair와 후속 패치가 깨질 수 있다. 참조 추적, 보존 만료, 백업 복구 시험을 운영 인수 조건으로 만든다. | BL-04, BL-20, BL-40, BL-43 |
| R11 | 높음 | 기존 설계가 EG-BIM 전용으로 읽히고 MSIX를 후속 선택으로 제외했다. | 범용 제작기·두 형식 필수·Store 별도 배포로 정정. 공통/형식별 모델·두 백엔드·검증·일정 v2 반영. | BL-46~53 |
## 범위 정리
이전 v2 범위 정정에서는 범용 제작기 완료 범위를 **EXE·MSIX 생성**으로 두었다. 현재 v3는 MSIX 먼저이며 아래 EXE·서버 항목은 후속이다. 장기 계약은 형식별 identity/서명/업데이트, Rust CLI/Tauri 제작 화면, EXE 과정/UI 편집과 MSP, 공통 검증·내보내기다. EG-BIM 실제 전환·기존 업데이터·자체 서버의 4단계 배포는 계획에 유지하되 별도 적용 트랙으로 판정한다. 이 트랙의 외부 자료 부족이 다른 제품의 로컬 패키지 생성을 막지 않는다.
Store 제출은 검토 범위이며 정식 출시의 공통 차단 조건으로 두지 않는다. 자동 승격, 독자 바이너리 차분 엔진, 모든 과거 버전 조합, 다중 기준 단일 MSP, MSIX 다중 아키텍처 bundle, ISM 자동 가져오기, 복잡한 운영 대시보드는 이번 기준 일정에 포함하지 않는다. 이 항목을 추가하면 범위와 일정을 다시 산정한다.
## 실행 계획
[구현 백로그](../backlog/README.md)는 위 발견을 53개 작업으로 구체화한다. [개발 일정과 마일스톤](10-development-schedule.md)은 **AI 지원 없이 단독 개발**하는 기준이며, 제작기·업데이터·서버 작업을 한 사람의 용량 안에서 순차 배치한다. 기존 문서의 단계 목록은 영역별 설계 설명으로 유지하고, 통합 실행 순서와 통과 조건은 이 일정 문서를 기준으로 추적한다.
외부 승인·테스터 모집·서명 계약의 실제 담당자, 지원 버전 목록, 주당 확보 가능한 시간은 아직 확정되지 않았다. 이 문서는 해당 결정을 대신 승인하지 않는다.
+56
View File
@@ -0,0 +1,56 @@
# 단독 개발 일정과 마일스톤 — MSIX 우선
기준선 v3: 2026-10-19, Asia/Seoul. 사용자의 “우선 MSIX를 만들어주는 프로그램” 요청에 따른 재배치다. **개발자 1명, AI 지원 없음**을 유지한다. [설계](../code-design/12-msix-first-builder.md), [백로그](../backlog/README.md), [변경 기록](../changes/2026-10-19-msix-first.md)을 함께 관리한다.
## 범위와 추정
첫 버전은 범용 MSIX 프로젝트·파일/manifest·MakeAppx·서명·불변 결과·직접 배포/App Installer·GUI/CLI·Windows VM 검증이다. EXE/MSI/MSP·HTML 설치 마법사·사용자 정의 BA·EG-BIM 전환·자체 인증/그룹 서버·Store 제출·bundle은 후속이다. 기존 53개 작업 중 18개를 1차로 배치하고 35개를 이월한다. 후속 작업을 완료 처리하거나 삭제하지 않는다.
현재 잠정 예산은 **24주 + 별도 완충 4주**다. 2026-10-26 시작 가정에서 MS6 목표는 **2027-04-09**, 완충 포함 계획 경계는 **2027-05-07**이다. 사용자가 확정한 출시일이나 실측 견적이 아니다. 기존 v2의 W38/2027-07-16 및 W52/2027-10-22 통합 계획은 이 기준선으로 대체한다. 후속 EXE·서버·Store의 새 날짜는 아직 배정하지 않는다.
MSIX만 먼저 만들므로 기존 52주에서 단순히 8주를 떼어내는 계산을 하지 않는다. 공통 프로젝트/파일/작업/서명/저장소/GUI 기반을 MSIX 경로에서 먼저 구현하는 공수를 포함해 재배치한다. MS0에서 작은 앱의 실제 도구 실험 후 다시 산정한다.
## 산정 조건
| 항목 | 기준 |
| --- | --- |
| 인력 | 개발자 1명이 학습·설계·구현·자체 리뷰·VM/수동 시험·문서·릴리스를 수행. 별도 QA 없음. |
| AI | AI 코드 생성·설계·리뷰·테스트·문서화의 시간 단축을 가정하지 않음. 일반 IDE/CI/자동 테스트는 사용. |
| 용량 | 전담 주 40시간 중 계획 작업 30시간, 대응·환경 정비·작은 재작업 여유 10시간. 실제 가용 시간 미확정. |
| 총 예산 | 24×30 = 약 720 계획 시간. 완충 4주는 별도이며 기능 작업을 숨겨 넣지 않음. |
| 작업 순서 | 구현 WIP 1. 엔진·UI·QA가 같은 사람의 시간임을 반영하고 병렬로 계산하지 않음. |
| 경험 | Windows 앱 개발 경험을 가정. Rust/Tauri/SDK 경험 부족분은 MS0에서 학습 기간 추가. |
| 달력 | 월~금 구간. 야근/주말 제외. 공휴일·휴가·회사 휴무는 착수 시 반영해 완충/목표일 조정. |
| 외부 의존성 | Windows VM·실제 서명 접근 확보. Store 계정·EG-BIM 자료·그룹 테스터는 1차 의존성이 아님. |
| UI | 두 시안 중 한 가지를 제품 기본 화면으로 선택. 시안이 이미 존재해도 실제 UI/IPC 구현 완료로 계산하지 않음. |
## 단계별 일정
| 마일스톤 | 주차 / 기간 | 예산 | 백로그 | 결과와 통과 조건 |
| --- | --- | --- | --- | --- |
| MS0 범위·위험 실험 | W1~3 / 2026-10-26 ~ 2026-11-13 | 3주 | BL-46/02/03/06 | 작은 앱의 MSIX 생성·서명·설치·업데이트와 도구/지원 OS/CPU 조합 확정. 공통화·서명 지연·학습 공수 재산정. |
| MS1 공통 엔진·CLI·VM | W4~7 / 2026-11-16 ~ 2026-12-11 | 4주 | BL-09/10/11/14/47/16/41 | MSIX-only v2 모델·입력 스냅샷·작업 수명·backend/CLI·VM 증빙. EXE 설정/도구 없이 동작. |
| MS2 MSIX 생성 | W8~11 / 2026-12-14 ~ 2027-01-08 | 4주 | BL-48/49 | manifest·identity·자산·의존성 검증과 MakeAppx pack. 실제 결과 내용 검사, 누락/취소/디스크 부족 시험. |
| MS3 서명·결과 보관 | W12~14 / 2027-01-11 ~ 2027-01-29 | 3주 | BL-50 | 개발/운영/미서명 구분, Publisher·신뢰·서명/해시 검사, 불변 결과·로그·증빙·내보내기. |
| MS4 직접 배포 업데이트 | W15~17 / 2027-02-01 ~ 2027-02-19 | 3주 | BL-51 | App Installer 설정/URI·버전 검증, 실제 업데이트·중단/재시도·실행 중 앱·데이터 보존. |
| MS5 제작기 UI | W18~21 / 2027-02-22 ~ 2027-03-19 | 4주 | BL-29/52 | 선택한 한 가지 레이아웃, 실파일/자산/의존성 편집, 서명·업데이트·진행/실패·접근성. CLI와 같은 결과. |
| MS6 MSIX 제품 완료 | W22~24 / 2027-03-22 ~ 2027-04-09 | 3주 | BL-53 | 두 독립 제품의 실제 생성·서명·설치·실행·업데이트·제거, 필수 실패 시험·사용 가이드·증빙 완성. |
| 완충 | W25~28 / 2027-04-12 ~ 2027-05-07 | 4주 | 지연된 1차 작업 | 기존 범위의 불확실성/외부 지연에만 사용. EXE·서버·Store 기능을 넣지 않음. |
MS2는 패키지 생성 중간 결과이며 제품 완료가 아니다. MS3 서명/보관과 MS4 업데이트 증빙, MS5 GUI가 연결된 뒤 MS6 회귀를 통과해야 한다. 미서명 개발 파일 생성이나 HTML 모의 빌드 성공을 실제 설치 가능/출시 완료로 표시하지 않는다.
## 실행 순서와 통과 조건
- MS0: 두 독립 가상 제품 선정, 지원 OS/CPU·SDK 고정, Publisher/서명·App Installer 경로 실험. 서명 접근 신청은 즉시 시작하며 발급 대기를 구현 완료로 세지 않는다.
- MS1: 프로젝트→파일 스냅샷→작업 엔진→backend 포트→CLI 순서로 연결. VM은 첫 실제 패키지부터 증빙을 수집하도록 준비한다. MSI GUID/레지스트리 컴파일은 구현하지 않는다.
- MS2: 단순 앱부터 자산·실행 항목·런타임/의존성을 확장. 미지원 서비스/드라이버/임의 설치 동작은 계획 단계에서 차단한다.
- MS3: 공통 Signer/ArtifactRepository를 MSIX 경로에서 구현. Publisher 불일치·신뢰 실패·타임스탬프 오류·중단/재시도·동일 releaseId 덮어쓰기 금지를 검사한다. 운영 서명 미확보 시 배포 준비 게이트는 통과 불가다.
- MS4: 실제 HTTPS 시험 호스트로 버전 증가·건너뛰기·다운로드 중단·앱 사용 중 업데이트·데이터 보존을 검사한다. 이 호스트 구성은 시험 예산에 포함하며 자체 장치 인증/4단계 배포 서버 구현으로 확장하지 않는다.
- MS5: 한 디자인으로 실제 파일·자산·의존성·서명 프로필·출력 폴더 편집과 작업 이벤트를 연결한다. 제작기 자체 배포/실행도 확인한다. 모의 시안의 즉시 완료 버튼은 실제 제품에 사용하지 않는다.
- MS6: 모든 1차 작업의 증빙을 실제 아티팩트 해시에 연결하고 사용 가이드·서명 오류·업데이트/복구 절차를 완성한다. 필수 notRun 항목이 남으면 통과시키지 않는다.
## 변경과 지연 관리
매주 완료 ID·증빙·다음 한 작업·차단 원인·잔여 기간을 갱신한다. 1주 이상 지연 전망, 도구/지원 범위 변경, 추가 런타임 요구가 생기면 뒤 날짜와 완충을 함께 수정한다. 실제 계획 시간이 주 30시간보다 작으면 남은 작업 시간을 실제 주당 시간으로 나눠 재산정한다. 외부 서명 대기는 단순 비례 축소하지 않는다.
MS0·MS2·MS4에서 공수를 다시 산정한다. 시안의 접근성/GUI·CLI 동등성, 실제 서명, 업데이트/제거 시험을 삭제해 날짜를 맞추지 않는다. EXE/HTML 설치 화면/Store 등 후속 요청을 1차에 넣으면 설계·백로그·일정을 함께 변경한다. 사용자 요청 없이 후속 개발 일정을 확정하지 않는다.
+14 -7
View File
@@ -1,15 +1,19 @@
# EG-BIM 네트워크 설치 및 배포 계획
# 범용 설치 패키지 제작 및 배포 계획
기준일: 2026-10-16, Asia/Seoul. 대상 프로그램: `D:\egbim\repo\14.1`.
**현재 기준:** [MSIX 우선 제작기](../code-design/12-msix-first-builder.md)와 [일정 v3](10-development-schedule.md). 이전 EXE/EG-BIM 중심 계획은 후속 적용 자료이며 첫 MSIX 출시의 선행 조건이 아니다.
EG-BIM의 기존 Windows 설치 구조를 유지하면서 InstallShield를 대체하고, 버전별 패키지 관리와 4단계 그룹 배포를 지원한다. Microsoft Store 배포와 코드 서명도 함께 준비한다.
기준일: 2026-10-16, Asia/Seoul. EG-BIM 적용 사례 경로: `D:\egbim\repo\14.1`. 제품 범위는 2026-10-19 정정했다.
제품은 **범용 Windows 설치 패키지 제작기**이며 현재 첫 버전은 MSIX 출력을 먼저 완성한다. EXE는 후속 단계다. EG-BIM의 InstallShield 전환과 4단계 그룹 배포는 적용·연계 트랙으로 유지한다. [형식과 배포 대상의 분리](../code-design/11-generic-exe-msix-architecture.md)를 전체 계획에 적용한다.
사용자가 확정한 사항은 Windows 데스크톱 앱 배포, InstallShield 사용 중단, 버전별 패키지 관리와 패치 지원, 업로드 체계, 개발자 테스터부터 일반 사용자까지의 4단계 배포다. Microsoft Store 배포는 검토 대상이다.
현재 권고안은 **WiX MSI + Burn 전체 설치 EXE + 자체 배포 서버 및 업데이터**다. Store에는 전체 설치 EXE를 등록하는 경로를 우선 검토한다. WiX 채택, Azure 서명 서비스 계약, 세부 운영 기준은 아직 확정되지 않았다.
EXE 백엔드는 **WiX MSI + Burn**, MSIX 백엔드는 **Windows SDK MakeAppx**를 사용한다. 자체 배포 서버·업데이터는 별도 연계다. Store에는 제작기가 생성한 EXE 또는 MSIX를 제품 적합성에 따라 등록한다. WiX 채택, Azure 서명 서비스 계약, 세부 운영 기준은 아직 확정되지 않았다.
## 문서 목록
실제 등록 절차는 [초보자를 위한 Microsoft Store 배포 매뉴얼](../manuals/microsoft-store/README.md)에서 확인한다.
생성 프로그램의 구현 설계는 [Rust + Tauri 설계 문서](../code-design/README.md)에 별도로 정리한다. 파일·레지스트리 편집, 버전/GUID, 전체 릴리스·MSP 생성, 대용량 분할 출력과 화면/CLI 계약을 다룬다.
| 문서 | 내용 |
@@ -22,16 +26,19 @@ EG-BIM의 기존 Windows 설치 구조를 유지하면서 InstallShield를 대
| [06 코드 서명과 SmartScreen](06-code-signing-and-smartscreen.md) | 서명 대상과 순서, 서비스 비용, 게시자, SmartScreen 한계 |
| [07 구현 순서와 검증 계획](07-implementation-and-validation.md) | 선행 결정, 구현 단계, 마이그레이션 및 배포 시험 |
| [08 버전별 패치](08-version-patching.md) | 기준 버전별 패치 선택, MSP, 누적 패치, 실패 복구 및 전체 설치 전환 |
| [09 계획 검토 결과](09-plan-review.md) | 문서 간 의존성·미확정 사항 검토와 백로그 연결 |
| [10 개발 일정과 마일스톤](10-development-schedule.md) | AI 지원 없는 단독 개발 기준 일정, 단계별 통과 조건과 완충 |
| [구현 백로그](../backlog/README.md) | 우선순위·선행 조건·완료 기준이 있는 1차 18개·후속 35개 작업 |
## 설계 원칙
1. 테스트부터 일반 배포까지 같은 서명 완료 패키지를 사용한다.
2. 파일 업로드와 사용자 공개를 별도 상태로 관리한다.
3. 기존 설치 경로와 사용자 설정을 보존한다.
4. 설치 대상 버전은 서버에서 인증된 사용자·장치와 정책으로 결정한다.
3. EXE 기존 설치 전환에서는 경로와 사용자 설정을 보존한다. MSIX 경로는 OS가 관리하며 앱 데이터 이전은 별도 검증한다.
4. 자체 그룹 배포를 사용하는 경우 대상 버전은 서버에서 인증된 사용자·장치와 정책으로 결정한다. 로컬 패키지 생성에 서버를 요구하지 않는다.
5. 배포 중지, 설치 중 실패 복구, 설치 완료 후 장애 복구를 구분한다.
6. 코드 서명과 SmartScreen 평판을 구분한다. Azure 서명도 경고 면제를 보장하지 않는다.
7. 버전별 패치를 필수 지원하며, 전체 설치 파일도 함께 유지한다. 적용 가능한 기준 버전과 설치 상태를 검증한 뒤 패치를 실행한다.
7. 첫 버전의 MSIX는 버전 증가 패키지와 App Installer로 업데이트한다. EXE/MSI의 전체 설치·MSP는 후속 범위로 유지한다. 적용 가능한 기준 버전과 설치 상태를 검증한 뒤 패치를 실행한다.
## 검토 범위
Binary file not shown.
File diff suppressed because it is too large. Load diff
+164
View File
@@ -0,0 +1,164 @@
<style>
#eg-wizard-design { width:100%; }
#eg-wizard-design .ew-stage { min-height:690px; }
#eg-wizard-design .ew-preview,#eg-wizard-design .ew-app { --ew-bg:light-dark(#ffffff,#20252d); --ew-soft:light-dark(#f4f6f9,#191e26); --ew-line:light-dark(#dce2ea,#3c4553); --ew-ink:light-dark(#202c3d,#edf2fa); --ew-muted:light-dark(#5f6d80,#b0bdce); --ew-blue:light-dark(#2656bd,#a6c0ff); --ew-on:light-dark(#ffffff,#112956); --ew-wash:light-dark(#edf3ff,#293a57); --ew-good:light-dark(#246b50,#94d9b7); --ew-warn:light-dark(#865517,#efc280); --ew-bad:light-dark(#a63532,#ffb0ac); color:var(--ew-ink); font:14px/1.6 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design * { box-sizing:border-box; }
#eg-wizard-design .ew-preview { display:flex; flex-wrap:wrap; align-items:flex-end; gap:8px; padding:14px 0; padding-right:92px; }
#eg-wizard-design .ew-preview label { display:grid; gap:4px; font-size:11px; color:var(--ew-muted); }
#eg-wizard-design .ew-preview select { max-width:100%; background:var(--ew-bg); color:var(--ew-ink); border:1px solid var(--ew-line); border-radius:5px; padding:6px 8px; font:12px/1.5 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design button { font:500 12px/1.5 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design .ew-button { display:inline-flex; justify-content:center; align-items:center; gap:7px; min-height:34px; padding:7px 16px; border:1px solid var(--ew-line); border-radius:5px; color:var(--ew-ink); background:var(--ew-bg); }
#eg-wizard-design button:disabled { opacity:.48; cursor:not-allowed; }
#eg-wizard-design .ew-primary { background:var(--ew-blue); color:var(--ew-on); border-color:var(--ew-blue); }
#eg-wizard-design .ew-link { background:transparent; border:0; color:var(--ew-blue); padding:5px 0; }
#eg-wizard-design .ew-app { background:var(--ew-bg); border:1px solid var(--ew-line); border-radius:9px; overflow-wrap:anywhere; box-shadow:0 8px 30px light-dark(#17305710,#00000025); }
#eg-wizard-design .ew-title { min-height:44px; display:flex; gap:9px; align-items:center; padding:10px 18px; border-bottom:1px solid var(--ew-line); font-size:12px; }
#eg-wizard-design .ew-mark { display:grid; place-items:center; width:23px; height:23px; background:var(--ew-blue); color:var(--ew-on); border-radius:5px; font-weight:600; flex-shrink:0; }
#eg-wizard-design .ew-tag { margin-left:auto; font-size:11px; color:var(--ew-muted); }
#eg-wizard-design .ew-layout { display:grid; grid-template-columns:154px minmax(0,1fr); min-height:480px; }
#eg-wizard-design .ew-side { background:var(--ew-soft); padding:28px 18px; border-right:1px solid var(--ew-line); }
#eg-wizard-design .ew-side-brand { font-size:17px; font-weight:600; letter-spacing:-.6px; margin-bottom:4px; }
#eg-wizard-design .ew-side-sub { color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-steps { list-style:none; display:grid; gap:21px; margin:34px 0 0; padding:0; }
#eg-wizard-design .ew-steps li { display:flex; align-items:center; gap:9px; color:var(--ew-muted); font-size:12px; }
#eg-wizard-design .ew-number { display:grid; place-items:center; width:22px; height:22px; border:1px solid var(--ew-line); border-radius:50%; font-size:11px; flex-shrink:0; }
#eg-wizard-design .ew-steps [aria-current=step] { color:var(--ew-blue); font-weight:600; }
#eg-wizard-design .ew-steps [aria-current=step] .ew-number { background:var(--ew-blue); color:var(--ew-on); border-color:transparent; }
#eg-wizard-design .ew-past .ew-number { color:var(--ew-good); }
#eg-wizard-design .ew-main { min-width:0; }
#eg-wizard-design .ew-page-heading { padding:23px 26px 18px; border-bottom:1px solid var(--ew-line); }
#eg-wizard-design .ew-page-heading h2 { margin:0 0 5px; font-size:19px; font-weight:600; letter-spacing:-.6px; }
#eg-wizard-design .ew-page-heading p { margin:0; color:var(--ew-muted); font-size:12px; }
#eg-wizard-design .ew-body { padding:22px 26px; }
#eg-wizard-design .ew-body h3 { font-size:15px; margin:0 0 10px; font-weight:600; }
#eg-wizard-design .ew-body p { margin:0 0 14px; font-size:13px; }
#eg-wizard-design .ew-muted { color:var(--ew-muted); }
#eg-wizard-design .ew-small { font-size:11px !important; }
#eg-wizard-design .ew-box { padding:14px 16px; border:1px solid var(--ew-line); border-radius:6px; margin:14px 0; background:var(--ew-bg); }
#eg-wizard-design .ew-box-top { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; margin-bottom:7px; }
#eg-wizard-design .ew-chip { display:inline-block; color:var(--ew-good); background:var(--ew-soft); font-size:11px; padding:2px 7px; border-radius:4px; }
#eg-wizard-design .ew-path { display:block; font-size:12px; color:var(--ew-muted); overflow-wrap:anywhere; }
#eg-wizard-design .ew-note { display:flex; align-items:flex-start; gap:9px; padding:12px; background:var(--ew-wash); color:var(--ew-blue); border-radius:5px; font-size:12px; margin-top:14px; }
#eg-wizard-design .ew-note svg { flex-shrink:0; margin-top:2px; }
#eg-wizard-design .ew-list { padding:0; margin:0; list-style:none; }
#eg-wizard-design .ew-list li { display:flex; justify-content:space-between; gap:12px; padding:9px 0; font-size:12px; border-bottom:1px solid var(--ew-line); }
#eg-wizard-design .ew-list li:last-child { border:0; }
#eg-wizard-design .ew-good { color:var(--ew-good); }
#eg-wizard-design .ew-bad { color:var(--ew-bad); }
#eg-wizard-design .ew-status { font-size:11px; white-space:nowrap; }
#eg-wizard-design .ew-field { display:grid; gap:6px; font-size:12px; margin:0 0 16px; }
#eg-wizard-design .ew-field input { width:100%; border:1px solid var(--ew-line); border-radius:5px; padding:9px 10px; background:var(--ew-bg); color:var(--ew-ink); font:12px/1.5 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design .ew-choice { display:flex; align-items:flex-start; gap:8px; padding:9px 0; font-size:12px; }
#eg-wizard-design .ew-choice input { accent-color:var(--ew-blue); margin:3px 0 0; flex-shrink:0; }
#eg-wizard-design .ew-choice span { display:block; }
#eg-wizard-design .ew-choice small { display:block; color:var(--ew-muted); font-size:11px; margin-top:2px; }
#eg-wizard-design .ew-rule { height:1px; background:var(--ew-line); margin:14px 0; }
#eg-wizard-design .ew-summary { display:grid; grid-template-columns:88px minmax(0,1fr); gap:10px 14px; font-size:12px; margin:0; }
#eg-wizard-design .ew-summary dt { color:var(--ew-muted); }
#eg-wizard-design .ew-summary dd { margin:0; }
#eg-wizard-design .ew-progress { background:var(--ew-soft); height:8px; border-radius:4px; overflow:hidden; margin:18px 0 7px; }
#eg-wizard-design .ew-progress span { display:block; background:var(--ew-blue); height:100%; }
#eg-wizard-design .ew-progress-label { display:flex; justify-content:space-between; gap:12px; color:var(--ew-muted); font-size:12px; margin-bottom:15px; }
#eg-wizard-design .ew-footer { border-top:1px solid var(--ew-line); background:var(--ew-soft); padding:14px 18px; border-radius:0 0 9px 9px; display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap; }
#eg-wizard-design .ew-footer-label { color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-actions { display:flex; gap:7px; flex-wrap:wrap; margin-left:auto; }
#eg-wizard-design .ew-symbol { display:grid; place-items:center; width:42px; height:42px; border-radius:50%; background:var(--ew-wash); color:var(--ew-blue); font-size:23px; margin:0 0 15px; }
#eg-wizard-design .ew-log { margin-top:16px; color:var(--ew-muted); font-size:11px; }
#eg-wizard-design .ew-log summary { width:fit-content; }
#eg-wizard-design .ew-log pre { white-space:pre-wrap; overflow-wrap:anywhere; font:11px/1.7 'Segoe UI','Malgun Gothic',sans-serif; }
#eg-wizard-design .ew-confirm { background:var(--ew-bg); border:1px solid var(--ew-line); border-radius:6px; padding:16px; margin-top:16px; }
#eg-wizard-design .ew-confirm .ew-actions { margin-top:12px; }
#eg-wizard-design .ew-compact .ew-layout { display:block; }
#eg-wizard-design .ew-compact .ew-side { display:none; }
#eg-wizard-design .ew-compact .ew-page-heading { padding-left:30px; padding-right:30px; background:var(--ew-soft); }
#eg-wizard-design .ew-compact .ew-body { max-width:610px; padding:24px 30px; margin:auto; }
#eg-wizard-design .ew-caption { color:var(--ew-muted); font-size:11px; margin:10px 0 0; }
@media(max-width:580px) { #eg-wizard-design .ew-stage { min-height:850px; } #eg-wizard-design .ew-layout { display:block; } #eg-wizard-design .ew-side { padding:12px 16px; border-right:0; border-bottom:1px solid var(--ew-line); } #eg-wizard-design .ew-side-brand,#eg-wizard-design .ew-side-sub { display:none; } #eg-wizard-design .ew-steps { display:flex; gap:7px; justify-content:space-between; margin:0; } #eg-wizard-design .ew-steps li { flex-direction:column; gap:5px; font-size:11px; } #eg-wizard-design .ew-page-heading,#eg-wizard-design .ew-compact .ew-page-heading { padding:18px; } #eg-wizard-design .ew-body,#eg-wizard-design .ew-compact .ew-body { padding:18px; } #eg-wizard-design .ew-preview { padding-right:0; padding-top:55px; } #eg-wizard-design .ew-title { padding:10px 14px; } #eg-wizard-design .ew-summary { grid-template-columns:70px minmax(0,1fr); } }
@media(pointer:coarse) { #eg-wizard-design button,#eg-wizard-design select,#eg-wizard-design .ew-choice { min-height:44px; } #eg-wizard-design .ew-field input { font-size:16px; } }
</style>
<div id="eg-wizard-design" class="viz-carousel viz-dotted-background" aria-label="EG-BIM 설치 마법사 재디자인" data-previous-label="이전 디자인" data-next-label="다음 디자인">
<section data-variant="진행 안내형" class="ew-stage" aria-label="진행 안내형 설치 마법사"><div id="ew-guided-tools" class="ew-preview"></div><div id="ew-guided" class="ew-app" aria-label="진행 안내형"></div></section>
<section data-variant="표준 마법사형" class="ew-stage" aria-label="표준 마법사형 설치 마법사" hidden><div id="ew-standard-tools" class="ew-preview"></div><div id="ew-standard" class="ew-app ew-compact" aria-label="표준 마법사형"></div></section>
</div>
<script>
(() => {
const root=document.getElementById('eg-wizard-design');
const escape=v=>String(v).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const modes={new:'신규 설치',upgrade:'기존 버전 전환',patch:'버전 패치',repair:'설치 복구',remove:'제거'};
const scenarios={normal:'정상 완료',backupFail:'백업 실패',applyFail:'설치 실패',reboot:'재부팅 필요',locked:'실행 중인 앱'};
const actions={new:'설치',upgrade:'업데이트',patch:'패치 적용',repair:'복구',remove:'제거'};
const basePath='C:\\Program Files\\Baron\\EG-BIM', legacyPath='D:\\Baron\\EG-BIM';
const defaults=()=>({mode:'upgrade',scenario:'normal',page:'welcome',agreed:false,path:basePath,resources:true,shortcut:true,removeConfirmed:false,launch:true,restart:false,confirmCancel:false,cancelled:false,error:'',closed:false});
function sequence(s) { return s.mode==='new'?['welcome','license','options','review','prepare','apply','finish']:['welcome','review','prepare','apply','finish']; }
function normalize(input) { const s=defaults(); if(!input||typeof input!=='object')return s; for(const key of Object.keys(s)){if(typeof input[key]===typeof s[key])s[key]=input[key];} if(!modes[s.mode])s.mode='upgrade';if(!scenarios[s.scenario])s.scenario='normal';if(![...sequence(s),'failed','cancelled'].includes(s.page))s.page='welcome'; if(s.scenario==='backupFail'&&!['upgrade','patch'].includes(s.mode))s.scenario='normal';return s; }
const saved=window.openai?.widgetState?.privateContent?.wizards||{};
const states={'ew-guided':normalize(saved['ew-guided']),'ew-standard':normalize(saved['ew-standard'])};
function next(s) {
s.error='';
if(s.page==='license'&&!s.agreed){s.error='사용권 계약에 동의해야 다음 단계로 이동할 수 있습니다.';return;}
if(s.page==='options'&&!/^[a-zA-Z]:\\[^<>:"|?*\r\n]+$/.test(s.path)){s.error='드라이브를 포함한 설치 경로를 입력해 주세요. 예: C:\\Program Files\\Baron\\EG-BIM';return;}
if(s.page==='review'&&s.mode==='remove'&&!s.removeConfirmed){s.error='제거할 항목을 확인하고 동의해 주세요.';return;}
const seq=sequence(s),index=seq.indexOf(s.page); if(index>=0&&index<seq.length-1)s.page=seq[index+1];
}
function simulate(s) {
if(s.confirmCancel)return;
if(s.page==='prepare') {
if(s.scenario==='locked'){s.error='EG-BIM에서 도면을 저장하고 종료한 후 다시 확인해 주세요.';return;}
s.page=s.scenario==='backupFail'?'failed':'apply';
} else if(s.page==='apply')s.page=s.cancelled||s.scenario==='applyFail'?'failed':'finish';
}
function row(label,value,tone='') {return `<li><span>${label}</span><span class="ew-status ${tone}">${value}</span></li>`;}
function note(text,icon='shield-check') {return `<div class="ew-note"><i data-lucide="${icon}" aria-hidden="true"></i><span>${text}</span></div>`;}
function choice(label,key,checked,sub='',disabled=false) {return `<label class="ew-choice"><input type="checkbox" data-value="${key}" ${checked?'checked':''} ${disabled?'disabled':''}><span>${label}${sub?`<small>${sub}</small>`:''}</span></label>`;}
function title(s) {
const a=actions[s.mode];
return {welcome:[s.mode==='new'?'EG-BIM 설치를 시작합니다':s.mode==='upgrade'?'기존 EG-BIM을 업데이트합니다':s.mode==='patch'?'새 버전의 변경 사항을 적용합니다':s.mode==='repair'?'EG-BIM 설치를 복구합니다':'EG-BIM을 제거합니다','계속하려면 내용을 확인하고 다음을 선택하세요.'],license:['사용권 계약','설치하기 전에 사용권 계약을 확인해 주세요.'],options:['설치 위치 및 옵션','설치할 위치와 추가 항목을 선택하세요.'],review:[`${a} 준비가 되었습니다`,'아래 내용을 확인하세요. 이전 단계로 돌아가 선택을 바꿀 수 있습니다.'],prepare:['설치 환경을 준비하고 있습니다','작업을 저장하고 실행 중인 EG-BIM을 종료해 주세요.'],apply:[s.cancelled?'취소 요청을 처리하고 있습니다':`${a} 작업을 진행하고 있습니다`,s.cancelled?'안전한 중단 지점과 현재 설치 상태를 확인합니다.':'작업이 끝날 때까지 PC를 끄지 마세요.'],finish:[s.scenario==='reboot'?'완료하려면 PC를 다시 시작해야 합니다':`${a} 작업이 완료되었습니다`,s.scenario==='reboot'?'열려 있는 작업을 저장한 후 다시 시작해 주세요.':'아래 내용을 확인하고 마침을 선택하세요.'],failed:[s.scenario==='backupFail'?'설정 백업을 완료하지 못했습니다':s.cancelled?'작업을 중단했습니다':'설치를 완료하지 못했습니다',s.scenario==='backupFail'?'기존 제품을 변경하지 않았습니다.':'현재 상태를 확인한 후 복구 절차를 진행해야 합니다.'],cancelled:['설치를 취소했습니다','프로그램을 변경하기 전에 종료했습니다.']}[s.page];
}
function body(s) {
const a=actions[s.mode], existing=s.mode!=='new', backup=['upgrade','patch'].includes(s.mode), path=existing?legacyPath:s.path;
if(s.page==='welcome')return `<div class="ew-symbol"><i data-lucide="${s.mode==='remove'?'package-minus':s.mode==='repair'?'wrench':'package-check'}" aria-hidden="true"></i></div><h3>${s.mode==='new'?'설계에 필요한 도구를 준비하세요.':s.mode==='upgrade'?'작업 환경은 유지하고, 프로그램은 새롭게.':s.mode==='patch'?'설치된 구성은 그대로 유지합니다.':s.mode==='repair'?'프로그램 파일과 설치 정보를 복구합니다.':'프로그램과 설치 항목을 제거합니다.'}</h3><p class="ew-muted">${s.mode==='remove'?'개인 도면, 사용자 설정과 보관된 백업은 유지합니다.':s.mode==='new'?'마법사의 안내에 따라 설치 위치와 옵션을 선택할 수 있습니다.':'작업 중인 도면을 저장한 후 진행해 주세요.'}</p><div class="ew-box"><div class="ew-box-top"><strong>EG-BIM</strong><span class="ew-chip">${existing?'기존 설치 확인':'Windows · x64'}</span></div><span class="ew-path">${escape(path)}</span><span class="ew-small ew-muted">${existing?'기존 설치 위치를 그대로 사용합니다.':'기본 설치 위치 · 다음 단계에서 변경 가능'}</span></div>${note(backup?'설정 백업을 확인한 후 프로그램 변경을 시작합니다.':s.mode==='remove'?'타사 CAD 연결과 사용자 데이터는 삭제 대상에서 제외합니다.':'시스템 변경이 필요한 경우 Windows가 관리자 권한을 요청합니다.')}`;
if(s.page==='license')return `<div class="ew-box"><h3>EG-BIM 소프트웨어 사용권 계약</h3><p class="ew-muted">설치 화면 검토용 약관 자리표시자입니다.</p><p class="ew-small ew-muted">실제 설치 파일에는 제품 담당자가 승인한 약관 전문과 버전을 표시합니다. 이 시안의 선택은 법적 계약 동의나 실제 설치를 수행하지 않습니다.</p></div>${choice('사용권 계약에 동의합니다.','agreed',s.agreed)}${note('동의 여부는 설치 시작 전까지 변경할 수 있습니다.','info')}`;
if(s.page==='options')return `<label class="ew-field">설치 위치<input data-path aria-label="설치 위치" value="${escape(s.path)}" spellcheck="false"></label><h3>설치 구성</h3>${choice('EG-BIM 프로그램','core',true,'필수 구성요소',true)}${choice('추가 리소스','resources',s.resources,'기능 선택 예시 · 실제 리소스 목록은 별도 확정')}<div class="ew-rule"></div>${choice('바탕 화면에 바로가기 만들기','shortcut',s.shortcut)}<p class="ew-small ew-muted">기존 CAD 파일 연결은 자동으로 변경하지 않습니다.</p>`;
if(s.page==='review')return `<dl class="ew-summary"><dt>작업</dt><dd>${modes[s.mode]}</dd><dt>${s.mode==='remove'?'제거 위치':'설치 위치'}</dt><dd>${escape(path)}${existing&&s.mode!=='remove'?' · 유지':''}</dd><dt>설치 구성</dt><dd>${s.mode==='remove'?'현재 설치된 프로그램 제거':existing?'현재 설치된 기능 유지':s.resources?'프로그램 + 추가 리소스':'프로그램'}</dd><dt>사용자 데이터</dt><dd>${backup?'설정·CUI·추가 리소스 백업 후 호환 설정 복원':s.mode==='remove'?'개인 도면·설정·백업 유지':'기존 데이터 유지'}</dd>${s.mode==='new'?`<dt>바로가기</dt><dd>${s.shortcut?'바탕 화면에 생성':'생성 안 함'}</dd>`:''}<dt>필수 구성</dt><dd>${s.mode==='remove'?'공유 런타임 유지':'필요한 런타임만 준비'}</dd></dl>${s.mode==='remove'?choice('프로그램을 제거하고 개인 데이터는 유지합니다.','removeConfirmed',s.removeConfirmed):note(backup?'파일 확보와 백업 검증에 실패하면 기존 제품을 제거하지 않습니다.':'설치 전 패키지와 저장 공간을 다시 확인합니다.')}<details class="ew-log"><summary class="cursor-interaction">예정 작업 자세히 보기</summary><p>환경 검사 → 파일 검증 → ${backup?'앱 종료 확인 · 설정 백업 → ':''}${a} → 상태 확인${backup?' · 설정 복원':''}</p></details>`;
if(s.page==='prepare')return `<ul class="ew-list">${row('지원 환경 · 저장 공간','확인 완료','ew-good')}${row('설치 파일 · 서명 · 무결성','확인 완료','ew-good')}${row('실행 중인 프로그램',s.scenario==='locked'?'EG-BIM 실행 중':'종료 확인',s.scenario==='locked'?'ew-bad':'ew-good')}${backup?row('사용자 설정 백업',s.scenario==='locked'?'대기':'검사 중'):row('실행 계획 준비','준비 중')}</ul>${s.scenario==='locked'?`${note('도면을 직접 저장하고 EG-BIM을 종료해 주세요. 강제 종료하지 않습니다.','info')}<button type="button" class="ew-button cursor-interaction" data-action="recheck" style="margin-top:12px">다시 확인</button>`:note(backup?'백업 위치: D:\\EG-BIM-Backup\\시도-001 (예시)':'현재까지 프로그램 파일은 변경하지 않았습니다.')}<p class="ew-small ew-muted" style="margin-top:16px">시안 진행은 창 위의 ‘작업 진행’으로 확인할 수 있습니다.</p>`;
if(s.page==='apply')return `<h3>${s.cancelled?'안전한 중단 지점 확인 중':s.mode==='remove'?'등록된 프로그램 파일 제거 중':'프로그램 파일 적용 중'}</h3><div class="ew-progress" role="progressbar" aria-label="진행률 예시" aria-valuemin="0" aria-valuemax="100" aria-valuenow="64"><span style="width:64%"></span></div><div class="ew-progress-label"><span>${s.cancelled?'취소 요청됨':'현재 작업 진행률'}</span><span>64%</span></div><ul class="ew-list">${row('환경 및 설치 파일 확인','완료','ew-good')}${backup?row('사용자 설정 백업 검증','완료','ew-good'):''}${row(a,s.cancelled?'중단 요청 처리 중':'진행 중')}${row('설치 상태 확인','대기')}</ul><details class="ew-log"><summary class="cursor-interaction">작업 내역 보기</summary><pre>시도: DEMO-001\n환경 검사: 완료\n패키지 검증: 완료\n${backup?'백업 검증: 완료\n':''}작업 상태: ${s.cancelled?'취소 처리 중':'적용 중'}\n이 내역은 시안용 예시입니다.</pre></details>`;
if(s.page==='failed')return `<div class="ew-symbol">!</div><h3>${s.scenario==='backupFail'?'백업 저장 위치를 확인해 주세요.':'진단 기록을 보관해 주세요.'}</h3><p class="ew-muted">${s.scenario==='backupFail'?'백업 위치의 공간 또는 쓰기 권한을 확인한 후 다시 검사할 수 있습니다.':'현재 설치 상태가 확인되지 않았습니다. 설치 파일을 다시 실행하거나 전체 설치로 덮어쓰기 전에 복구 상태를 확인해야 합니다.'}</p><ul class="ew-list">${row('프로그램 상태',s.scenario==='backupFail'?'기존 상태 유지':'확인 필요',s.scenario==='backupFail'?'ew-good':'ew-bad')}${row('백업',backup?(s.scenario==='backupFail'?'검증되지 않음':'보관됨'):'해당 없음')}${row('다음 조치',s.scenario==='backupFail'?'백업 위치 확인':'진단 및 복구')}</ul><details class="ew-log" open><summary class="cursor-interaction">진단 기록</summary><pre>시도: DEMO-001\n상태: ${s.scenario==='backupFail'?'BackupValidationFailed':s.cancelled?'CancellationStateUncertain':'InstallStateUncertain'}\n로그: 설치 로그 보관 위치 (예시)\n이 시안은 실제 시스템 상태를 조회하지 않습니다.</pre></details>`;
if(s.page==='cancelled')return `<div class="ew-symbol">−</div><h3>다음에 다시 시작할 수 있습니다.</h3><p class="ew-muted">설치 전 취소되어 기존 프로그램과 사용자 데이터는 유지됩니다.</p>`;
const reboot=s.scenario==='reboot';
return `<div class="ew-symbol">${reboot?'↻':'✓'}</div><ul class="ew-list">${row(a,'완료','ew-good')}${backup?row('호환 설정 복원','완료','ew-good'):''}${row('설치 상태',reboot?'재부팅 후 확인':'확인 완료',reboot?'':'ew-good')}</ul>${reboot?`${choice('지금 다시 시작','restart',s.restart,'선택하지 않으면 나중에 직접 재부팅합니다.')}<p class="ew-small ew-muted">시안에서는 실제 재부팅하지 않습니다.</p>`:s.mode!=='remove'?choice('마침 후 EG-BIM 실행','launch',s.launch,'원래 사용자 권한으로 실행'):'<p class="ew-small ew-muted" style="margin-top:15px">개인 도면·사용자 설정·백업은 유지됩니다.</p>'}${backup?note('백업은 유지됩니다. 도면·출력·인증·플러그인을 확인한 후 정리하세요.'):''}${s.closed?'<p class="ew-small ew-muted" role="status" style="margin-top:12px">미리보기를 마쳤습니다. 실제 실행이나 재부팅은 하지 않았습니다.</p>':''}`;
}
function persist(id) {window.openai?.setWidgetState?.({modelContent:{design:id==='ew-guided'?'진행 안내형':'표준 마법사형',mode:states[id].mode,page:states[id].page,scenario:states[id].scenario},privateContent:{wizards:states}})?.catch(()=>{});}
function render(id,focus=false) {
const s=states[id],app=document.getElementById(id),tools=document.getElementById(id+'-tools');
tools.innerHTML=`<label>시안 · 설치 상황<select class="cursor-interaction" data-mode aria-label="시안 설치 상황">${Object.entries(modes).map(([k,v])=>`<option value="${k}" ${s.mode===k?'selected':''}>${v}</option>`).join('')}</select></label><label>시험할 상태<select class="cursor-interaction" data-scenario aria-label="시험할 상태">${Object.entries(scenarios).filter(([k])=>k!=='backupFail'||['upgrade','patch'].includes(s.mode)).map(([k,v])=>`<option value="${k}" ${s.scenario===k?'selected':''}>${v}</option>`).join('')}</select></label><button class="ew-button cursor-interaction" data-demo="advance" ${!['prepare','apply'].includes(s.page)||s.confirmCancel?'disabled':''}>작업 진행</button><button class="ew-button cursor-interaction" data-demo="reset">처음부터</button>`;
tools.querySelector('[data-mode]').addEventListener('change',e=>{states[id]=defaults();states[id].mode=e.target.value;render(id);persist(id);});
tools.querySelector('[data-scenario]').addEventListener('change',e=>{s.scenario=e.target.value;s.page='welcome';s.error='';s.cancelled=false;s.confirmCancel=false;s.closed=false;render(id);persist(id);});
tools.querySelector('[data-demo=advance]').addEventListener('click',()=>{simulate(s);render(id);persist(id);});
tools.querySelector('[data-demo=reset]').addEventListener('click',()=>{states[id]={...defaults(),mode:s.mode,scenario:s.scenario};render(id);persist(id);});
const [heading,sub]=title(s),seq=sequence(s),index=seq.indexOf(s.page);
const groups=s.mode==='new'?['시작','선택','확인','진행','완료']:['시작','확인','진행','완료'];
const current=(s.mode==='new'?{welcome:0,license:1,options:1,review:2,prepare:3,apply:3,finish:4,failed:3,cancelled:0}:{welcome:0,review:1,prepare:2,apply:2,finish:3,failed:2,cancelled:0})[s.page];
const busy=['prepare','apply'].includes(s.page),end=['finish','failed','cancelled'].includes(s.page);
const button= end?(s.page==='failed'&&s.scenario==='backupFail'?'다시 검사':s.page==='finish'?'마침':'닫기'):s.page==='review'?`${actions[s.mode]} 시작`:busy?s.cancelled?'취소 처리 중…':'진행 중…':'다음';
app.innerHTML=`<header class="ew-title"><span class="ew-mark">E</span><span>EG-BIM 설치 마법사</span><span class="ew-tag">${modes[s.mode]}</span></header><div class="ew-layout"><aside class="ew-side"><div class="ew-side-brand">EG-BIM</div><div class="ew-side-sub">BARON SOFTWARE</div><ol class="ew-steps">${groups.map((n,i)=>`<li ${i===current?'aria-current="step"':''} class="${i<current?'ew-past':''}"><span class="ew-number">${i<current?'✓':i+1}</span>${n}</li>`).join('')}</ol></aside><main class="ew-main"><header class="ew-page-heading"><h2>${heading}</h2><p>${sub}</p></header><div class="ew-body"><div aria-live="polite">${body(s)}</div>${s.error?`<p class="ew-small ew-bad" role="alert">${escape(s.error)}</p>`:''}${s.confirmCancel?`<section class="ew-confirm" aria-label="취소 확인"><h3>작업을 취소하시겠습니까?</h3><p class="ew-small ew-muted">${s.page==='apply'?'안전한 중단 지점까지 기다린 후 설치 상태를 확인합니다.':'아직 프로그램을 변경하지 않았습니다.'}</p><div class="ew-actions"><button class="ew-button cursor-interaction" data-action="keep">계속 진행</button><button class="ew-button cursor-interaction" data-action="confirm">취소 요청</button></div></section>`:''}</div></main></div><footer class="ew-footer"><span class="ew-footer-label">시안 · 표시 정보는 예시</span><div class="ew-actions"><button class="ew-button cursor-interaction" data-action="back" ${index<=0||busy||end||s.confirmCancel?'disabled':''}>이전</button><button class="ew-button ew-primary cursor-interaction" data-action="next" ${busy||s.confirmCancel||s.page==='license'&&!s.agreed||s.page==='review'&&s.mode==='remove'&&!s.removeConfirmed?'disabled':''}>${button}</button>${!end?`<button class="ew-button cursor-interaction" data-action="cancel" ${s.cancelled||s.confirmCancel?'disabled':''}>취소</button>`:''}</div></footer>`;
app.querySelector('[data-path]')?.addEventListener('input',e=>{s.path=e.target.value;});
app.querySelectorAll('[data-value]').forEach(el=>el.addEventListener('change',e=>{s[el.dataset.value]=e.target.checked;render(id);persist(id);app.querySelector(`[data-value="${el.dataset.value}"]`)?.focus({preventScroll:true});}));
app.querySelector('[data-action=back]').addEventListener('click',()=>{if(index>0&&!busy&&!end){s.page=seq[index-1];s.error='';render(id,true);persist(id);}});
app.querySelector('[data-action=next]').addEventListener('click',()=>{
if(s.page==='failed'&&s.scenario==='backupFail'){s.scenario='normal';s.page='prepare';}
else if(end){s.closed=true;if(s.page!=='finish'){s.page='welcome';s.cancelled=false;s.closed=false;}}
else next(s);
render(id,true);persist(id);
});
app.querySelector('[data-action=cancel]')?.addEventListener('click',()=>{s.confirmCancel=true;render(id);app.querySelector('[data-action=keep]')?.focus({preventScroll:true});});
app.querySelector('[data-action=keep]')?.addEventListener('click',()=>{s.confirmCancel=false;render(id,true);});
app.querySelector('[data-action=confirm]')?.addEventListener('click',()=>{s.confirmCancel=false;if(s.page==='apply')s.cancelled=true;else s.page='cancelled';render(id,true);persist(id);});
app.querySelector('[data-action=recheck]')?.addEventListener('click',()=>{s.scenario='normal';s.error='';render(id);persist(id);});
if(globalThis.lucide)lucide.createIcons({attrs:{width:16,height:16}});
if(focus&&!busy)app.querySelector('[data-action=next]')?.focus({preventScroll:true});
}
Object.keys(states).forEach(id=>render(id));
window.addEventListener('openai:set_globals',e=>{const restored=e.detail?.globals?.widgetState?.privateContent?.wizards;if(restored)Object.keys(states).forEach(id=>{states[id]=normalize(restored[id]);render(id);});});
if(globalThis.Tweak)Object.keys(states).forEach(id=>{const app=document.getElementById(id),design={radius:9};const tweak=new Tweak({container:app,onChange:()=>{app.style.borderRadius=design.radius+'px';}});tweak.addSlider(design,'radius',{label:'창 모서리',min:0,max:16,unit:'px'});});
})();
</script>
+177
View File
@@ -0,0 +1,177 @@
<div id="msix-builder-concept">
<style>
#msix-builder-concept { font-family: "Segoe UI", "Malgun Gothic", sans-serif; color-scheme: light dark; }
#msix-builder-concept * { box-sizing: border-box; }
#msix-builder-concept [hidden] { display: none !important; }
#msix-builder-concept .ms-app { --ms-bg: light-dark(#f6f7fa,#171b23); --ms-paper: light-dark(#ffffff,#202630); --ms-text: light-dark(#202a39,#edf1f8); --ms-muted: light-dark(#59677b,#aebacf); --ms-line: light-dark(#dce2eb,#3a4353); --ms-accent: light-dark(#245bd7,#96b7ff); --ms-soft: light-dark(#edf3ff,#273752); --ms-button: light-dark(#245bd7,#a9c3ff); --ms-onbutton: light-dark(#ffffff,#152445); --ms-good: light-dark(#17654c,#8bd7b6); --ms-warn: light-dark(#805011,#edc27e); --ms-error: light-dark(#a52c33,#ffabb0); color:var(--ms-text); background:var(--ms-bg); border:1px solid var(--ms-line); border-radius:12px; overflow:hidden; min-height:940px; display:flex; flex-direction:column; font-size:14px; line-height:1.55; }
#msix-builder-concept button, #msix-builder-concept input, #msix-builder-concept select { font:inherit; }
#msix-builder-concept button { color:inherit; cursor:inherit; }
#msix-builder-concept .ms-top { display:flex; align-items:center; gap:12px; padding:16px 22px; padding-right:104px; border-bottom:1px solid var(--ms-line); background:var(--ms-paper); flex-wrap:wrap; }
#msix-builder-concept .ms-brand { font-size:16px; font-weight:600; display:flex; gap:10px; align-items:center; }
#msix-builder-concept .ms-mark { display:grid; place-items:center; width:32px; height:32px; border-radius:9px; background:var(--ms-button); color:var(--ms-onbutton); }
#msix-builder-concept .ms-pill { padding:3px 9px; border-radius:5px; background:var(--ms-soft); color:var(--ms-accent); font-size:12px; }
#msix-builder-concept .ms-caption { color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-spacer { flex:1; }
#msix-builder-concept .ms-layout { flex:1; display:grid; grid-template-columns:190px minmax(0,1fr); }
#msix-builder-concept .ms-nav { padding:23px 12px; border-right:1px solid var(--ms-line); }
#msix-builder-concept .ms-project { padding:0 12px 23px; overflow-wrap:anywhere; }
#msix-builder-concept .ms-project strong { display:block; font-weight:600; margin:4px 0; }
#msix-builder-concept .ms-nav button { display:flex; align-items:center; gap:11px; width:100%; border:0; background:transparent; padding:11px 12px; text-align:left; border-radius:7px; margin-bottom:5px; }
#msix-builder-concept .ms-nav button[aria-current="step"] { background:var(--ms-soft); color:var(--ms-accent); font-weight:600; }
#msix-builder-concept .ms-nav svg { flex:none; }
#msix-builder-concept .ms-nav .ms-nav-end { margin:30px 12px 0; padding-top:15px; border-top:1px solid var(--ms-line); }
#msix-builder-concept .ms-content { min-width:0; padding:27px 28px 20px; }
#msix-builder-concept h2 { margin:5px 0 7px; font-size:25px; line-height:1.35; font-weight:600; letter-spacing:-.6px; }
#msix-builder-concept h3 { margin:0 0 17px; font-size:15px; font-weight:600; }
#msix-builder-concept p { margin:0; }
#msix-builder-concept .ms-eyebrow { font-size:11px; letter-spacing:1.4px; font-weight:600; color:var(--ms-accent); }
#msix-builder-concept .ms-lead { color:var(--ms-muted); margin-bottom:25px; }
#msix-builder-concept .ms-cols { display:grid; grid-template-columns:minmax(0,1.65fr) minmax(190px,1fr); gap:20px; align-items:start; }
#msix-builder-concept .ms-panel { background:var(--ms-paper); border:1px solid var(--ms-line); border-radius:10px; padding:20px; min-width:0; }
#msix-builder-concept .ms-fields { display:grid; grid-template-columns:1fr 1fr; gap:17px; }
#msix-builder-concept .ms-full { grid-column:1/-1; }
#msix-builder-concept label.ms-field { display:flex; flex-direction:column; gap:6px; font-size:12px; font-weight:600; }
#msix-builder-concept input:not([type="checkbox"]), #msix-builder-concept select { width:100%; min-width:0; padding:9px 10px; border:1px solid var(--ms-line); border-radius:6px; color:var(--ms-text); background:var(--ms-paper); font-weight:400; }
#msix-builder-concept input:focus-visible, #msix-builder-concept select:focus-visible, #msix-builder-concept button:focus-visible { outline:2px solid var(--ms-accent); outline-offset:3px; }
#msix-builder-concept .ms-field small { color:var(--ms-muted); font-size:11px; font-weight:400; }
#msix-builder-concept .ms-line { height:1px; background:var(--ms-line); margin:20px 0; }
#msix-builder-concept .ms-note { margin-top:16px; padding:12px 14px; background:var(--ms-soft); border-radius:6px; color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-appicon { display:grid; place-items:center; width:56px; height:56px; border-radius:13px; background:var(--ms-button); color:var(--ms-onbutton); margin-bottom:17px; }
#msix-builder-concept .ms-appicon svg { width:27px; height:27px; }
#msix-builder-concept .ms-preview-name { font-size:19px; font-weight:600; overflow-wrap:anywhere; margin-bottom:4px; }
#msix-builder-concept .ms-preview-description { font-size:12px; color:var(--ms-muted); min-height:38px; overflow-wrap:anywhere; }
#msix-builder-concept .ms-kv { display:flex; align-items:flex-start; gap:14px; justify-content:space-between; font-size:12px; padding:8px 0; }
#msix-builder-concept .ms-kv span:first-child { color:var(--ms-muted); flex-shrink:0; }
#msix-builder-concept .ms-kv span:last-child { overflow-wrap:anywhere; text-align:right; min-width:0; }
#msix-builder-concept .ms-button { display:inline-flex; justify-content:center; align-items:center; gap:7px; background:var(--ms-paper); border:1px solid var(--ms-line); border-radius:6px; padding:9px 14px; font-weight:500; min-height:39px; }
#msix-builder-concept .ms-button.primary { background:var(--ms-button); border-color:transparent; color:var(--ms-onbutton); }
#msix-builder-concept .ms-button:disabled { opacity:.5; cursor:not-allowed; }
#msix-builder-concept .ms-button.full { width:100%; margin-top:14px; }
#msix-builder-concept .ms-footer { display:flex; gap:12px; justify-content:space-between; align-items:center; margin-top:24px; flex-wrap:wrap; }
#msix-builder-concept .ms-actions { display:flex; gap:8px; flex-wrap:wrap; }
#msix-builder-concept .ms-status { font-size:12px; color:var(--ms-muted); }
#msix-builder-concept .ms-check { display:flex; gap:10px; align-items:flex-start; margin:13px 0; }
#msix-builder-concept input[type="checkbox"] { width:17px; height:17px; accent-color:var(--ms-accent); margin:3px 0 0; flex:none; }
#msix-builder-concept .ms-check small { display:block; font-size:12px; color:var(--ms-muted); margin-top:3px; }
#msix-builder-concept .ms-toolbar { display:flex; justify-content:space-between; gap:10px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }
#msix-builder-concept .ms-toolbar h3 { margin:0; }
#msix-builder-concept .ms-file { display:flex; align-items:center; gap:11px; padding:13px 0; border-bottom:1px solid var(--ms-line); min-width:0; }
#msix-builder-concept .ms-file:last-child { border-bottom:0; }
#msix-builder-concept .ms-file .ms-filename { flex:1; min-width:0; overflow-wrap:anywhere; }
#msix-builder-concept .ms-file small { display:block; color:var(--ms-muted); font-size:11px; }
#msix-builder-concept .ms-file > span:last-child { font-size:12px; color:var(--ms-muted); white-space:nowrap; }
#msix-builder-concept .ms-file svg { color:var(--ms-muted); flex:none; }
#msix-builder-concept .ms-choice { display:flex; align-items:center; gap:10px; padding:14px; border:1px solid var(--ms-line); border-radius:7px; margin-bottom:10px; }
#msix-builder-concept .ms-choice.active { border-color:var(--ms-accent); background:var(--ms-soft); }
#msix-builder-concept .ms-choice strong { display:block; font-size:13px; font-weight:600; }
#msix-builder-concept .ms-choice small { color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-good { color:var(--ms-good); }
#msix-builder-concept .ms-warn { color:var(--ms-warn); }
#msix-builder-concept .ms-error { color:var(--ms-error); }
#msix-builder-concept .ms-alert { padding:13px 15px; border:1px solid var(--ms-line); border-left:3px solid var(--ms-error); background:var(--ms-paper); border-radius:6px; margin-bottom:17px; font-size:12px; }
#msix-builder-concept .ms-alert button { margin-top:10px; }
#msix-builder-concept .ms-stage { display:flex; align-items:flex-start; gap:12px; padding:14px 0; border-bottom:1px solid var(--ms-line); }
#msix-builder-concept .ms-stage:last-child { border:0; }
#msix-builder-concept .ms-stage .ms-stage-title { flex:1; min-width:0; }
#msix-builder-concept .ms-stage small { display:block; color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-stage > span:last-child { font-size:12px; }
#msix-builder-concept .ms-meter { height:6px; border-radius:4px; background:var(--ms-line); overflow:hidden; margin:12px 0; }
#msix-builder-concept .ms-meter div { height:100%; background:var(--ms-accent); }
#msix-builder-concept .ms-code { font:12px/1.8 Consolas,monospace; white-space:pre-wrap; overflow-wrap:anywhere; color:var(--ms-muted); margin:0; }
#msix-builder-concept .ms-bottom { display:flex; gap:10px; justify-content:space-between; padding:11px 22px; border-top:1px solid var(--ms-line); background:var(--ms-paper); flex-wrap:wrap; font-size:11px; color:var(--ms-muted); }
#msix-builder-concept .ms-steps { display:grid; grid-template-columns:repeat(6,1fr); gap:8px; padding:22px 26px 0; }
#msix-builder-concept .ms-steps button { border:0; background:transparent; padding:8px 0; display:flex; flex-direction:column; align-items:center; gap:7px; color:var(--ms-muted); font-size:12px; }
#msix-builder-concept .ms-step-dot { width:27px; height:27px; border:1px solid var(--ms-line); border-radius:50%; display:grid; place-items:center; background:var(--ms-paper); font-size:12px; }
#msix-builder-concept .ms-steps button[aria-current="step"] { color:var(--ms-accent); }
#msix-builder-concept .ms-steps button[aria-current="step"] .ms-step-dot { background:var(--ms-button); color:var(--ms-onbutton); border-color:transparent; }
#msix-builder-concept .ms-guided .ms-content { flex:1; padding:24px 40px; }
#msix-builder-concept .ms-guided .ms-cols { grid-template-columns:minmax(0,1.6fr) minmax(210px,1fr); }
#msix-builder-concept .ms-guided { --ms-accent:light-dark(#146d60,#82d7c6); --ms-soft:light-dark(#eaf5f2,#263e3a); --ms-button:light-dark(#146d60,#9ae1d2); --ms-onbutton:light-dark(#ffffff,#14332e); }
@media(max-width:800px) { #msix-builder-concept .ms-layout { grid-template-columns:154px minmax(0,1fr); } #msix-builder-concept .ms-content { padding:22px 18px; } #msix-builder-concept .ms-cols, #msix-builder-concept .ms-guided .ms-cols { grid-template-columns:1fr; } #msix-builder-concept .ms-side { display:grid; grid-template-columns:1fr 1fr; gap:16px; } #msix-builder-concept .ms-side > .ms-caption { grid-column:1/-1; } #msix-builder-concept .ms-guided .ms-content { padding:22px; } }
@media(max-width:520px) { #msix-builder-concept .ms-app { min-height:1900px; } #msix-builder-concept .ms-layout { grid-template-columns:1fr; } #msix-builder-concept .ms-nav { border-right:0; border-bottom:1px solid var(--ms-line); padding:12px; display:grid; grid-template-columns:1fr 1fr; gap:3px; } #msix-builder-concept .ms-project, #msix-builder-concept .ms-nav-end { display:none; } #msix-builder-concept .ms-nav button { padding:8px; margin:0; font-size:12px; } #msix-builder-concept .ms-fields, #msix-builder-concept .ms-side { grid-template-columns:1fr; } #msix-builder-concept .ms-top { padding:14px 16px; padding-right:96px; } #msix-builder-concept .ms-top .ms-caption { display:none; } #msix-builder-concept .ms-content, #msix-builder-concept .ms-guided .ms-content { padding:20px 14px; } #msix-builder-concept .ms-panel { padding:16px; } #msix-builder-concept .ms-steps { padding:12px; grid-template-columns:repeat(3,1fr); } #msix-builder-concept .ms-footer { align-items:stretch; } #msix-builder-concept .ms-actions { flex:1; justify-content:flex-end; } #msix-builder-concept .ms-bottom { padding:10px 14px; } }
@media(pointer:coarse) { #msix-builder-concept .ms-button, #msix-builder-concept .ms-nav button, #msix-builder-concept .ms-steps button { min-height:44px; } #msix-builder-concept input:not([type="checkbox"]), #msix-builder-concept select { font-size:16px; } }
</style>
<div class="viz-carousel" aria-label="MSIX 제작기 디자인" data-previous-label="이전 시안" data-next-label="다음 시안">
<section data-variant="프로젝트 작업실" aria-label="프로젝트 작업실">
<div class="ms-app" data-app="studio"></div>
</section>
<section data-variant="단계별 제작" aria-label="단계별 제작" hidden>
<div class="ms-app ms-guided" data-app="guided"></div>
</section>
</div>
<script>
(() => {
const root = document.getElementById('msix-builder-concept');
const pages = [['app','앱 정보','panels-top-left'],['files','파일 구성','folder-open'],['manifest','패키지 설정','package'],['sign','서명','shield-check'],['update','업데이트','refresh-cw'],['build','검증 · 빌드','hammer']];
const defaults = () => ({page:'app',name:'Orbit Notes',description:'팀의 아이디어를 한곳에',publisherName:'Example Studio',identity:'ExampleStudio.OrbitNotes',publisher:'CN=Example Studio',version:'1.2.0.0',arch:'x64',exe:'OrbitNotes.exe',minOs:'10.0.19041.0',sign:'development',certSubject:'CN=Example Studio',autoUpdate:false,url:'https://downloads.example.com/orbit/stable/OrbitNotes.appinstaller',interval:'24',prompt:true,extraFile:false,revision:1,validated:false,build:'idle',log:false,message:'',error:''});
const state = {studio:defaults(),guided:defaults()};
const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2);
const esc = v => String(v).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const icon = name => `<i data-lucide="${name}" aria-hidden="true"></i>`;
const field = (s,key,label,full=false,hint='') => `<label class="ms-field ${full?'ms-full':''}">${label}<input data-field="${key}" value="${esc(s[key])}" ${key==='url'?'type="url"':'type="text"'}>${hint?`<small>${hint}</small>`:''}</label>`;
const select = (s,key,label,opts) => `<label class="ms-field">${label}<select data-field="${key}">${opts.map(([v,l])=>`<option value="${v}" ${s[key]===v?'selected':''}>${l}</option>`).join('')}</select></label>`;
const check = (s,key,label,hint='') => `<label class="ms-check"><input type="checkbox" data-field="${key}" ${s[key]?'checked':''}><span>${label}${hint?`<small>${hint}</small>`:''}</span></label>`;
const kv=(a,b)=>`<div class="ms-kv"><span>${a}</span><span>${b}</span></div>`;
function validate(s) {
if(!s.name.trim()||!s.identity.trim()||!s.publisher.trim()) return ['app','제품 이름과 패키지 식별 정보를 입력해 주세요.'];
if(!/^\d+\.\d+\.\d+\.\d+$/.test(s.version)||s.version.split('.').some(n=>Number(n)>65535)||Number(s.version.split('.')[0])===0) return ['app','버전을 1.2.0.0처럼 네 부분 숫자로 입력해 주세요.'];
if(!['OrbitNotes.exe'].includes(s.exe)) return ['files','실행 파일이 패키지 목록에 없습니다. OrbitNotes.exe를 지정해 주세요.'];
if(s.sign!=='none' && s.publisher!==s.certSubject) return ['sign','Publisher와 인증서 Subject가 일치하지 않습니다.'];
if(s.autoUpdate) { try { const u=new URL(s.url); if(u.protocol!=='https:'||!u.pathname.endsWith('.appinstaller')) throw Error(); } catch { return ['update','HTTPS 주소와 .appinstaller 파일 이름을 확인해 주세요.']; } }
return null;
}
function restore(saved) {
if(!saved||saved.schema!==1)return;
['studio','guided'].forEach(k=>{
const d=defaults(), v=saved[k];if(!v||typeof v!=='object')return;
Object.keys(d).forEach(f=>{if(typeof v[f]===typeof d[f]&&(!['string'].includes(typeof v[f])||v[f].length<1000))d[f]=v[f];});
if(!pages.some(p=>p[0]===d.page))d.page='app';
if(!['development','production','none'].includes(d.sign))d.sign='development';
d.validated=false;d.build='idle';d.error='';d.message='';state[k]=d;
});
}
restore(window.openai?.widgetState?.privateContent);
function persist(){ const host=window.openai;if(host?.setWidgetState)host.setWidgetState({modelContent:{design:'MSIX-first builder',studioPage:state.studio.page,guidedPage:state.guided.page},privateContent:{schema:1,sessionId,...state}}).catch(()=>{}); }
function preview(s) {
return `<aside class="ms-side"><div class="ms-panel"><h3>앱 표시 미리보기</h3><div class="ms-appicon">${icon('notebook-pen')}</div><div class="ms-preview-name">${esc(s.name||'앱 이름')}</div><p class="ms-preview-description">${esc(s.description)}</p><div class="ms-line"></div>${kv('게시자',esc(s.publisherName))}${kv('버전',esc(s.version))}${kv('아키텍처',esc(s.arch))}${kv('설치 위치','Windows에서 관리')}<div class="ms-note">설치 폴더 선택 없이 패키지로 배포합니다.</div></div><p class="ms-caption" style="margin-top:12px">표시 정보 예시입니다. 실제 App Installer 화면은 Windows 환경에 따라 달라집니다.</p></aside>`;
}
function side(s,title,body){return `<aside class="ms-side"><div class="ms-panel"><h3>${title}</h3>${body}</div></aside>`;}
function pane(s) {
if(s.page==='app')return `<div class="ms-cols"><div class="ms-panel"><h3>사용자에게 표시할 정보</h3><div class="ms-fields">${field(s,'name','앱 이름',true)}${field(s,'description','설명',true)}${field(s,'publisherName','게시자 표시명',true)}${field(s,'version','패키지 버전',false,'네 부분 숫자 버전')}${select(s,'arch','아키텍처',[['x64','x64'],['arm64','ARM64']])}</div><div class="ms-line"></div><div class="ms-toolbar"><span>앱 아이콘</span><span class="ms-caption">Assets / Logo.png · 예시</span></div><p class="ms-caption">실제 제품에서는 로고 파일과 크기를 검사합니다.</p></div>${preview(s)}</div>`;
if(s.page==='files')return `<div class="ms-cols"><div class="ms-panel"><div class="ms-toolbar"><h3>패키지에 포함할 파일</h3><button class="ms-button cursor-interaction" data-action="add-file">${icon(s.extraFile?'minus':'plus')}${s.extraFile?'예제 파일 제거':'예제 파일 추가'}</button></div><p class="ms-caption">소스 폴더 · D:\\releases\\orbit-notes</p>${[['OrbitNotes.exe','앱 실행 파일','18.4 MB'],['Orbit.Core.dll','애플리케이션 라이브러리','6.2 MB'],['Assets/Logo.png','앱 이미지 · 예시','24 KB'],['Resources/ko-KR.json','한국어 리소스','12 KB'],...(s.extraFile?[['Resources/en-US.json','추가한 예제 파일','10 KB']]:[])].map(([n,d,z])=>`<div class="ms-file">${icon(n.endsWith('exe')?'file-code':'file')}<div class="ms-filename">${esc(n)}<small>${d}</small></div><span>${z}</span></div>`).join('')}<div class="ms-line"></div>${field(s,'exe','실행 파일',true,'패키지 내부 상대 경로')}</div>${side(s,'파일 구성 원칙',`${kv('포함 파일',`${s.extraFile?5:4}개 · 예시`)}${kv('배치 위치','패키지 내부')}${kv('원본 파일','변경하지 않음')}<div class="ms-note">문서·설정·로그는 앱 데이터 위치를 사용합니다. 설치 폴더 쓰기에 의존하는 앱은 호환성을 확인해야 합니다.</div>`)}</div>`;
if(s.page==='manifest')return `<div class="ms-cols"><div class="ms-panel"><h3>패키지 식별 정보</h3><div class="ms-fields">${field(s,'identity','패키지 이름 · Identity Name',true)}${field(s,'publisher','Publisher',true,'서명 인증서의 Subject와 일치해야 합니다.')}${select(s,'minOs','최소 Windows 버전',[['10.0.19041.0','Windows 10 · 2004 이상'],['10.0.22000.0','Windows 11 이상']])}</div><div class="ms-line"></div><h3>앱 실행 방식</h3><div class="ms-choice active">${icon('monitor')}<div><strong>일반 데스크톱 앱</strong><small>Windows.FullTrustApplication</small></div></div><p class="ms-caption">runFullTrust 선언을 포함합니다. 서비스·드라이버·임의 설치 작업은 초기 지원 범위에 포함하지 않습니다.</p></div>${side(s,'생성할 manifest',`<pre class="ms-code">&lt;Identity\n Name="${esc(s.identity)}"\n Publisher="${esc(s.publisher)}"\n Version="${esc(s.version)}"\n ProcessorArchitecture="${esc(s.arch)}" /&gt;</pre><div class="ms-note">미리보기는 요약입니다. 실제 생성 시 SDK 스키마·자산·의존성을 함께 검사합니다.</div>`)}</div>`;
if(s.page==='sign')return `<div class="ms-cols"><div class="ms-panel"><h3>서명 프로필</h3>${select(s,'sign','사용할 프로필',[['development','개발용 인증서 · 예시'],['production','운영 서명 프로필 · 예시'],['none','미서명 개발 빌드']])}<div class="ms-line"></div>${s.sign==='none'?'<p class="ms-warn">미서명 패키지는 직접 배포 준비 상태가 될 수 없습니다.</p>':`${field(s,'certSubject','인증서 Subject · 예시',true)}<div class="ms-line"></div>${kv('패키지 Publisher',esc(s.publisher))}${kv('이름 일치',s.publisher===s.certSubject?'<span class="ms-good">일치</span>':'<span class="ms-error">불일치</span>')}`}<div class="ms-note">개인 키나 인증서 암호를 프로젝트에 저장하지 않습니다. 실제 제품에서는 OS 또는 서명 서비스 프로필을 참조합니다.</div></div>${side(s,'배포 전 확인',`<div class="ms-stage">${icon('shield-check')}<div class="ms-stage-title">패키지 서명<small>서명자·체인·타임스탬프 검사</small></div></div><div class="ms-stage">${icon('monitor-check')}<div class="ms-stage-title">대상 PC 신뢰<small>개발용 인증서 성공과 구분</small></div></div><div class="ms-note">이 시안은 인증서 저장소에 접근하거나 서명하지 않습니다.</div>`)}</div>`;
if(s.page==='update')return `<div class="ms-cols"><div class="ms-panel"><h3>직접 배포</h3><div class="ms-choice active">${icon('globe')}<div><strong>내 서버에서 배포</strong><small>Store 계정 없이 MSIX를 전달합니다.</small></div></div>${check(s,'autoUpdate','App Installer 업데이트 사용','MSIX와 함께 .appinstaller 파일을 생성합니다.')}${s.autoUpdate?`<div class="ms-line"></div><div class="ms-fields">${field(s,'url','업데이트 안내 파일 주소',true,'패키지 파일은 버전별 고정 URL을 사용합니다.')}${select(s,'interval','실행 시 확인 간격',[['0','앱 실행마다'],['24','24시간'],['48','48시간']])}</div>${check(s,'prompt','업데이트 안내 표시')}`:'<div class="ms-note">MSIX 파일을 직접 전달합니다. 자동 업데이트 경로는 연결되지 않습니다.</div>'}</div>${side(s,'생성할 결과',`${kv('설치 패키지','.msix')}${kv('업데이트 안내',s.autoUpdate?'.appinstaller':'사용 안 함')}${kv('업데이트 적용','Windows 배포 기능')}<div class="ms-note">새 버전 전체 패키지를 생성합니다. Windows의 차등 업데이트를 이용하며 별도 MSP 파일을 만들지 않습니다.</div><p class="ms-caption" style="margin-top:14px">Store 등록·자동 업로드·그룹 배포 서버는 후속 단계입니다.</p>`)}</div>`;
const completed=s.build==='done',failed=s.build==='failed',ready=s.validated;
const stages=[['설정과 파일 검사','manifest · 입력 · 경로','list-checks'],['MSIX 패키징','Windows SDK · MakeAppx','package'],['서명과 무결성',s.sign==='none'?'미서명 개발 결과':'서명 프로필 · 해시','shield-check'],['결과 보관','불변 아티팩트 · 검증 보고서','archive']];
return `<div class="ms-cols"><div class="ms-panel"><div class="ms-toolbar"><h3>빌드 계획</h3><span class="ms-pill">${completed?'모의 빌드 완료':failed?'검증 오류':ready?'모의 검사 통과':'검사 대기'}</span></div>${stages.map(([t,d,i],n)=>`<div class="ms-stage">${icon(completed?'circle-check':i)}<div class="ms-stage-title">${t}<small>${d}</small></div><span class="${completed?'ms-good':'ms-caption'}">${completed?(s.sign==='none'&&n===2?'서명 생략':'모의 완료'):'대기'}</span></div>`).join('')}<div class="ms-meter" role="progressbar" aria-label="모의 빌드 진행" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${completed?100:0}"><div style="width:${completed?100:0}%"></div></div><div class="ms-actions"><button class="ms-button cursor-interaction" data-action="validate">설정 검사</button><button class="ms-button primary cursor-interaction" data-action="build">${icon('play')}모의 빌드</button></div>${completed?`<div class="ms-line"></div><h3>결과 예시</h3><pre class="ms-code">OrbitNotes_${esc(s.version)}_${esc(s.arch)}.msix\n${s.autoUpdate?'OrbitNotes.appinstaller\n':''}release.json\nvalidation-report.json</pre><button class="ms-button cursor-interaction" data-action="log" style="margin-top:14px">${s.log?'기록 닫기':'모의 기록 보기'}</button>${s.log?'<pre class="ms-code" style="margin-top:12px">[예시] 입력 스냅샷 고정\n[예시] MakeAppx 단계 완료\n[예시] 서명 정책 적용\n[예시] 결과 목록 생성\n실제 명령 실행 및 파일 생성 없음</pre>':''}`:''}</div>${side(s,'릴리스 준비 상태',`${kv('패키지 생성',completed?'모의 완료':'대기')}${kv('서명',s.sign==='none'?'생략':completed?'모의 완료':'대기')}${kv('설치·실행 시험','미실행')}${kv('업데이트·제거 시험','미실행')}<div class="ms-note">${s.sign==='development'?'개발용 인증서로 설정되어 있습니다. 실제 사용자 배포에는 대상 PC 신뢰와 설치 시험이 필요합니다.':'빌드와 배포 준비는 별도 상태입니다. 실제 VM 검증 증빙이 필요합니다.'}</div>`)}</div>`;
}
const descriptions={app:'앱 이름과 버전을 입력하면 패키지 표시 정보를 미리 확인할 수 있습니다.',files:'배포할 파일을 모으고 앱의 실행 파일을 지정합니다.',manifest:'Windows가 앱을 식별할 이름과 실행 조건을 설정합니다.',sign:'패키지 게시자와 서명 프로필을 연결합니다.',update:'MSIX만 전달하거나, 내 서버를 통한 업데이트 경로를 연결합니다.',build:'생성 전 설정을 검사하고 패키지와 검증 결과를 확인합니다.'};
function render(k) {
const app=root.querySelector(`[data-app="${k}"]`),s=state[k],idx=pages.findIndex(p=>p[0]===s.page);
const nav=pages.map(([id,label,ic],n)=>`<button class="cursor-interaction" data-page="${id}" ${s.page===id?'aria-current="step"':''}>${k==='guided'?`<span class="ms-step-dot">${n+1}</span>`:icon(ic)}<span>${label}</span></button>`).join('');
const error=s.error?`<div class="ms-alert" role="alert">${esc(s.error)}${s.errorPage?`<br><button class="ms-button cursor-interaction" data-page="${esc(s.errorPage)}">해당 설정으로 이동</button>`:''}</div>`:'';
app.innerHTML=`<header class="ms-top"><div class="ms-brand"><span class="ms-mark">${icon('package')}</span>MSIX Studio</div><span class="ms-pill">디자인 시안</span><span class="ms-spacer"></span><span class="ms-caption">직접 배포용 패키지 제작</span></header>${k==='guided'?`<nav class="ms-steps" aria-label="제작 단계">${nav}</nav>`:'<div class="ms-layout">'+`<nav class="ms-nav" aria-label="프로젝트 설정"><div class="ms-project"><span class="ms-caption">현재 프로젝트</span><strong>${esc(s.name)}</strong><span class="ms-pill">MSIX · ${esc(s.arch)}</span></div>${nav}<div class="ms-nav-end ms-caption">Windows 패키지 제작<br>단일 제품 · 단일 아키텍처</div></nav>`}<main class="ms-content"><div class="ms-eyebrow">${k==='guided'?`STEP ${idx+1} OF 6`:'PACKAGE WORKSPACE'}</div><h2>${pages[idx][1]}</h2><p class="ms-lead">${descriptions[s.page]}</p>${error}${pane(s)}<footer class="ms-footer"><div class="ms-status" role="status" aria-live="polite">${esc(s.message||'예제 데이터 · 실제 파일 접근 없음')}</div><div class="ms-actions">${idx>0?'<button class="ms-button cursor-interaction" data-action="prev">이전</button>':''}${idx<5?'<button class="ms-button primary cursor-interaction" data-action="next">다음 단계 '+icon('arrow-right')+'</button>':''}</div></footer></main>${k==='studio'?'</div>':''}<div class="ms-bottom"><span>프로젝트 변경 ${s.revision} · ${esc(s.version)}</span><span>실제 패키지 생성·서명·설치 없음</span></div>`;
if(globalThis.lucide)globalThis.lucide.createIcons({attrs:{width:16,height:16}});
}
function runValidation(s){const err=validate(s);s.error=err?err[1]:'';s.errorPage=err?err[0]:'';s.validated=!err;s.message=err?'설정을 수정한 후 다시 검사해 주세요.':'모의 설정 검사 통과 · SDK와 실제 파일 검사는 미실행';return !err;}
root.addEventListener('click',e=>{
const b=e.target.closest('button'),app=e.target.closest('[data-app]');if(!b||!app)return;const k=app.dataset.app,s=state[k];
if(b.dataset.page){s.page=b.dataset.page;s.error='';s.errorPage='';}
const a=b.dataset.action,idx=pages.findIndex(p=>p[0]===s.page);
if(a==='next'||a==='prev'){s.page=pages[Math.max(0,Math.min(5,idx+(a==='next'?1:-1)))][0];s.error='';s.errorPage='';}
if(a==='add-file'){s.extraFile=!s.extraFile;s.revision++;s.validated=false;s.build='idle';s.message=s.extraFile?'예제 파일을 추가했습니다.':'추가한 예제 파일을 제거했습니다.';}
if(a==='validate')runValidation(s);
if(a==='build'){s.build=runValidation(s)?'done':'failed';if(s.build==='done')s.message='모의 빌드 완료 · 실제 산출물과 설치 증빙은 없습니다.';}
if(a==='log')s.log=!s.log;
render(k);persist();
});
root.addEventListener('change',e=>{
const f=e.target.dataset.field,app=e.target.closest('[data-app]');if(!f||!app)return;const k=app.dataset.app,s=state[k];s[f]=e.target.type==='checkbox'?e.target.checked:e.target.value;s.revision++;s.validated=false;s.build='idle';s.error='';s.errorPage='';s.message='설정이 변경되었습니다. 빌드 전 다시 검사합니다.';render(k);persist();
});
window.addEventListener('openai:set_globals',e=>{const saved=e.detail?.globals?.widgetState?.privateContent;if(!saved||saved.schema!==1||saved.sessionId===sessionId)return;restore(saved);render('studio');render('guided');});
render('studio');render('guided');
if(globalThis.Tweak){['studio','guided'].forEach(k=>{const app=root.querySelector(`[data-app="${k}"]`),design={corners:12};const tweak=new Tweak({container:app,onChange:()=>{app.style.borderRadius=design.corners+'px';}});tweak.addSlider(design,'corners',{label:'창 모서리',min:0,max:20,step:2,unit:'px'});});}
})();
</script>
</div>
File diff suppressed because it is too large. Load diff
+11
View File
@@ -0,0 +1,11 @@
/target/
**/dist/
**/node_modules/
**/gen/
**/.work/
**/releases/
**/package-project.local.json
**/*.lockfile
**/*.bak
/apps/desktop/src-tauri/icons/android/
/apps/desktop/src-tauri/icons/ios/
+5347
View File
File diff suppressed because it is too large. Load diff
+23
View File
@@ -0,0 +1,23 @@
[workspace]
members = ["crates/package-domain", "crates/package-engine", "crates/package-infra", "apps/cli", "apps/desktop/src-tauri"]
default-members = ["crates/package-domain", "crates/package-engine", "crates/package-infra", "apps/cli"]
resolver = "2"
[workspace.package]
version = "0.1.0"
edition = "2021"
rust-version = "1.94"
license = "MIT"
[workspace.dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
schemars = "1"
sha2 = "0.10"
tempfile = "3"
fs2 = "0.4"
uuid = { version = "1", features = ["v4", "serde"] }
package-domain = { path = "crates/package-domain" }
package-engine = { path = "crates/package-engine" }
package-infra = { path = "crates/package-infra" }
+162
View File
@@ -0,0 +1,162 @@
# Installer Studio — 구현 시작 버전
기존 MSIX 우선 설계를 실행 가능한 Rust/Tauri 프로그램으로 옮긴 0.1 개발 버전이다. 모든 소스와 잠금 파일은 이 디렉터리에 있다. **미서명 또는 개발자 자체 서명 MSIX를 생성하며 일반 배포 준비 상태를 부여하지 않는다.**
## 실행
Windows, Rust 1.94.0/MSVC 빌드 도구, Node.js 22.12 이상, WebView2가 필요하다. 실제 패키징에는 Windows SDK의 MakeAppx.exe가 필요하다. SDK·인증서는 프로그램이 자동 설치하지 않는다.
```powershell
cd D:\networkSetup\src\apps\desktop
npm.cmd ci
npm.cmd run tauri dev
```
새 프로젝트를 만들거나 `.isproj` 파일을 연 뒤 앱 정보 → 파일 구성에서 소스 폴더 연결 → 패키지 설정 → 검증·빌드에서 SDK 도구 선택 → 입력 검사 → 빌드 계획 생성 → MSIX 빌드 순서로 사용한다. 포함/제외 규칙과 의존성은 프로젝트 JSON 편집에서 수정한다. 기본 프로젝트의 Publisher는 예시이며 자신의 패키지 identity에 맞게 바꿔야 한다.
시작 화면은 왼쪽 **최근 프로젝트 목록**, 오른쪽 **프로젝트 열기 / 새 프로젝트 만들기** 버튼으로 구성한다. 최근 목록은 이 버전의 GUI에서 열거나 만든 프로젝트를 최대 20개 보관하며 앱을 다시 실행해도 유지된다. 이름·경로 검색과 오늘/이번 주/이번 달/이전 그룹을 제공한다. 목록의 `×`는 기록만 제거하며 프로젝트 파일을 삭제하지 않는다. 이동·삭제된 파일은 위치 확인 안내를 표시한다.
편집 화면 상단의 **프로젝트 닫기**로 시작 화면에 돌아간다. 미저장 변경이 있으면 **저장 후 닫기 / 저장 안 함 / 취소**를 선택하며, 저장 오류가 발생하면 프로젝트와 편집 내용을 유지한다. 다른 프로젝트 열기·새로 만들기에도 같은 보호 절차를 적용한다. 빌드 중에는 프로젝트를 닫을 수 없다.
최근 목록은 Tauri의 앱 데이터 폴더(기본 Windows 경로 `%APPDATA%\dev.networksetup.msixstudio\recent-projects.json`)에 저장한다. 프로젝트 경로·표시명·최근 열기 시각만 기록한다. 목록 저장 실패가 프로젝트 열기 자체를 차단하지는 않는다.
프런트엔드만 `npm.cmd run dev`로 실행하면 UI를 볼 수 있지만 파일 작업은 할 수 없다. 데스크톱은 임의 경로를 받는 파일/셸 플러그인 없이 Rust의 파일 선택 대화상자와 현재 프로젝트 세션을 사용한다.
실행 파일을 빌드하려면:
```powershell
# src/apps/desktop
npm.cmd run build
cd ../..
cargo build --locked -p msix-studio --features custom-protocol
# src/target/debug/msix-studio.exe
```
## VS Code 실행·디버깅
저장소 최상위 **`D:\networkSetup` 폴더**를 VS Code로 연다. 루트 `.vscode` 설정은 `src`의 Rust workspace를 연결한다. 확장 탭의 권장 확장인 **C/C++ (`ms-vscode.cpptools`)**와 **rust-analyzer**를 설치한다. Windows/MSVC용 `cppvsdbg` 구성은 [Tauri 공식 디버깅 안내](https://v2.tauri.app/develop/debug/vscode/)를 따른다.
1. `실행 및 디버그` (`Ctrl+Shift+D`)에서 **Installer Studio: Desktop (Rust)**를 선택한다.
2. `src/apps/desktop/src-tauri/src/main.rs` 또는 `src/crates`의 Rust 코드에 중단점을 설정하고 **F5**를 누른다.
3. 프런트엔드 의존성 설치 → TypeScript/Vite 빌드 → Rust debug 빌드 후 앱이 디버거에서 실행된다. 빌드된 UI를 포함하므로 Vite 서버는 시작하지 않는다. UI 변경도 F5로 다시 빌드하여 반영한다.
데스크톱 F5는 잠금 파일 기준으로 `npm ci`를 실행하므로 기존 Vite/Tauri 개발 서버를 먼저 종료한다. Rust 코드를 변경하고 재실행할 때도 이전 디버그 세션을 종료한다. `Ctrl+F5`는 디버거 없이 실행하고, `Ctrl+Shift+B`는 데스크톱 빌드만 수행한다. 이 구성의 중단점 대상은 Rust이며 TypeScript/WebView 디버깅은 별도다.
VS Code 빌드/시험 산출물은 `src/target/vscode`에 분리한다. 기존 `src/target/debug/msix-studio.exe`를 실행 중이어도 디버그 빌드와 충돌하지 않는다. 최초 VS Code 빌드는 의존성을 별도로 컴파일하므로 시간이 더 걸릴 수 있다.
| 실행 구성 | 동작 |
| --- | --- |
| `Installer Studio: Desktop (Rust)` | MSIX 제작기 GUI와 Rust 공통 엔진 디버깅 |
| `MSIX CLI: Schema` | 추가 입력 없이 JSON Schema 출력; CLI 중단점 확인용 |
| `MSIX CLI: Validate project` | 입력한 JSON 프로젝트 검사 |
| `MSIX CLI: Plan release` | 프로젝트 경로와 대상 ID를 받아 계획 생성 |
| `MSIX CLI: Build plan` | 프로젝트 경로와 새 계획 UUID를 받아 실제 MSIX 생성 |
CLI 입력 경로는 절대 경로나 `src` 기준 상대 경로를 따옴표 없이 입력한다. 기본값 `fixtures/InstallerProject.isproj`은 Publisher가 미확정인 초안이므로 검증 오류를 확인하는 용도다. 실제 빌드는 소스/SDK가 연결된 프로젝트를 사용한다. 시험 앱을 생성했다면 `.work/demo/FixtureAlpha/InstallerProject.isproj`을 입력할 수 있다. 계획 ID는 `Plan release` 결과의 `id`를 사용한다.
명령 팔레트의 `Tasks: Run Task`에서 `MSIX: test Rust`를 실행할 수 있다. MakeAppx 통합 시험은 기존 `scripts/Check.ps1 -MakeAppx ...`로 명시 실행한다.
## 레지스트리 설정
**레지스트리** 탭에서 값을 추가·수정·삭제한 뒤 저장하고 새 빌드 계획을 생성한다. `HKLM\Software` 아래 앱 전용 설정, 기본값, 32/64비트 뷰, `REG_SZ`/`REG_EXPAND_SZ`/`REG_DWORD`/`REG_QWORD`/`REG_MULTI_SZ`/`REG_BINARY`를 지원한다. QWORD를 포함한 모든 데이터는 프로젝트 JSON의 문자열로 보관한다.
빌드 엔진은 Windows의 `offreg.dll`로 실제 `registry.dat`를 생성하고, 패키지 역추출 후 해시와 각 값을 다시 검증한다. 빌드 PC의 일반 레지스트리는 변경하지 않는다. 릴리스와 내보내기에 `registry-report.json`을 포함한다. 이전 v2 프로젝트에 `registry` 필드가 없어도 빈 설정으로 열린다.
HKCU 초기화·COM/파일 연결·ISM 가져오기와 MSI 속성 치환은 아직 지원하지 않는다. 기존 EG-BIM 샘플에 레지스트리 설정을 자동 추가하지 않았으며, 새 기능을 사용한 프로젝트는 다시 빌드해야 한다. [설정 예시와 실제 실행 시험 기록](../docs/changes/2026-10-22-msix-registry-editor.md)
## CLI
아래 명령은 `src`에서 실행한다. stdout은 JSON, 오류는 stderr JSON이다. 개발자 서명 모드는 실제 MSIX 서명을 수행한다. 앱 설치와 PC의 인증서 신뢰 등록은 자동 수행하지 않는다.
```powershell
cargo run -p package-builder -- init --project D:\packages\demo\InstallerProject.isproj
cargo run -p package-builder -- validate --project D:\packages\demo\InstallerProject.isproj
cargo run -p package-builder -- plan release --project D:\packages\demo\InstallerProject.isproj --target msix-direct
cargo run -p package-builder -- build --project D:\packages\demo\InstallerProject.isproj --plan-id <계획의-id>
cargo run -p package-builder -- job status --project D:\packages\demo\InstallerProject.isproj --id <계획의-id>
cargo run -p package-builder -- export --project D:\packages\demo\InstallerProject.isproj --release <계획의-id> --output D:\exports\new-folder
cargo run -p package-builder -- schema
```
프로젝트는 `.isproj`만 지원하며 내부 내용은 UTF-8 JSON v2다. 새 프로젝트의 기본 이름은 `InstallerProject.isproj`이고, 확장자를 생략하면 `.isproj`를 붙인다. 기존 `.json` 프로젝트의 열기·호환·변환은 제공하지 않는다. 기존 JSON 항목은 최근 목록에서도 숨긴다.
별도의 프로젝트 ID는 사용하지 않는다. MSIX 앱 계열은 패키지 설정의 Identity Name과 Publisher로 관리한다. 기존 `projectId` 필드가 포함된 개발용 파일은 자동 변환하지 않고 알 수 없는 필드로 거부한다. 현재 fixture와 샘플 생성 스크립트는 해당 필드 없이 프로젝트를 만든다.
프로젝트 옆 `<프로젝트 파일명>.local.json`에서 소스 alias와 SDK를 연결한다. 예를 들어 `InstallerProject.isproj.local.json`이다. 상대 소스 경로는 프로젝트 폴더 기준이다. 기존 `package-project.local.json`은 읽지 않으며 local 파일은 Git에 넣지 않는다. 저장 시 이전 프로젝트는 `.isproj.bak`, 이전 로컬 설정은 `.isproj.local.json.bak`으로 보관한다.
```json
{
"sources": { "appPayload": "payload" },
"makeAppx": "C:/Program Files (x86)/Windows Kits/10/bin/10.0.26100.0/x64/makeappx.exe"
}
```
소스는 `.work`, `releases`를 포함하거나 그 아래에 위치할 수 없다. 예를 들어 프로젝트 폴더 전체 대신 그 안의 `payload` 폴더를 연결한다. 실행 항목은 x86/x64/arm64 PE이며 패키지 아키텍처와 같아야 한다. 이 버전의 로고는 scale-100 기준 44×44, 150×150 PNG만 지원한다.
생성물은 `.work/plans/<id>.json`, `.work/jobs/<id>/`, `releases/<id>/`에 보존된다. 계획에는 프로젝트/local revision, 소스별 SHA-256, MakeAppx 경로·해시를 저장하고 실행 전에 재검사한다. 같은 계획은 다시 실행하지 않으며 실패 후에는 새 계획을 만든다. `Ctrl+C`로 CLI 빌드를 취소할 수 있다. 종료된 작업의 `Running` 상태는 `job status`에서 잠금 소유자가 없을 때 `Interrupted`로 복구한다.
종료 코드: 0 명령 성공, 2 입력/계획 오류, 4 도구 오류, 5 아티팩트/identity 오류, 6 잠금 충돌, 130 취소. 빌드 성공은 배포 가능 판정이 아니다.
## 개발자 서명 모드
**서명 → 개발자 서명 · 테스트용 자체 서명**을 선택하고 **테스트 인증서 생성**을 누른다. 기존 인증서를 사용할 때는 **인증서 목록 새로고침 → 인증서 선택 → 선택한 인증서 사용** 순서로 진행한다. `Publisher`와 정확히 일치하는 Subject, 유효 기간, 코드 서명 용도, 개인 키를 검사한다. MakeAppx.exe와 같은 폴더의 SignTool.exe가 필요하다.
프로젝트에는 `"signing": { "mode": "development" }`, 로컬 설정에는 `developerCertificateThumbprint`를 저장한다. 기존 프로젝트의 기본값은 `unsigned`다. 인증서를 선택하지 않은 개발자 서명 빌드는 오류로 중단하며 미서명으로 대체하지 않는다. CLI에서는 `certificate --project <경로> --create`, 기존 인증서 선택은 `--thumbprint <지문>`, 목록은 추가 옵션 없이 실행한다.
인증서는 현재 사용자 개인 저장소에 생성하고 3개월간 유효하다. 개인 키는 내보내지 않으며 다음 빌드에서 선택한 인증서를 재사용한다. 패키징 → MSIX 서명 → 서명 및 신뢰 검사 → 역추출·내용 검사 순서로 처리한다. 결과에 공개 인증서 `development-signing.cer`, `signing-report.json`, `sign.log`, 관리자용 `Install-TestCertificate.ps1`을 함께 출력한다. 설치할 테스트 PC에서 공개 인증서를 확인한 뒤 스크립트를 관리자 권한으로 실행하면 해당 인증서만 `LocalMachine\TrustedPeople`에 등록한다. 제작기는 이 스크립트를 자동 실행하지 않는다.
서명 성공과 PC 신뢰 여부는 별도로 표시한다. 자체 서명은 Microsoft의 게시자 신원 보증이 아니며 Windows 개발자 모드와도 별개다. 내부 EXE/DLL 개별 서명, 공인 서명 서비스, 타임스탬프, 자동 설치 시험은 아직 제공하지 않는다. [구현 및 검증 기록](../docs/changes/2026-10-23-developer-signing.md)
## EG-BIM 샘플의 개발용 서명 후처리
기존 미서명 릴리스에는 `0x800B010A` 설치 오류에 대응하여 추가했던 `scripts/Sign-DevelopmentMsix.ps1`을 계속 사용할 수 있다. **제작기로 만든 검증된 미서명 릴리스**의 해시를 확인하고 복사본에 Windows SDK SignTool로 서명한다. 새 빌드에는 위의 통합 개발자 서명 모드를 사용하며, 이미 서명된 릴리스에 후처리를 중복 적용하지 않는다.
```powershell
.\scripts\Sign-DevelopmentMsix.ps1 -Release '<프로젝트 폴더>\releases\<빌드 ID>\release.json' -Output '<새 서명 출력 폴더>'
# 기존 개발용 인증서를 재사용하려면 -CertificateThumbprint <지문> 추가
# 새 빌드부터 이어서 서명하려면 Build-EgbimMsixSample.ps1에 -SignDevelopment 추가
```
Publisher와 같은 Subject의 단기 개발용 인증서를 현재 사용자의 개인 저장소에 생성한다. 개인 키는 내보내지 않고 공개 CER만 출력한다. 출력에는 서명본, SignTool 로그, CMS 서명 검사 및 신뢰 검사 결과를 기록한다. 신뢰되지 않은 자체 서명 인증서는 SignTool verify가 실패하며 설치 가능한 상태로 표시하지 않는다. 설치하려면 테스트 PC의 관리자가 공개 CER를 `LocalMachine\TrustedPeople`에 등록해야 한다. 스크립트는 인증서 신뢰 등록이나 앱 설치를 수행하지 않는다.
## 구조
| 경로 | 역할 |
| --- | --- |
| `crates/package-domain` | 엄격한 v2 DTO, 진단, 경로/버전/대상 검증, manifest 생성 |
| `crates/package-engine` | 검증/계획 계약, 백엔드 포트, 파일·도구 스냅샷 |
| `crates/package-infra` | 스트림 해시/복사, 파일 수집, OS 잠금, 원자적 JSON 저장, MakeAppx, 패키지 재검사, 결과 내보내기 |
| `apps/cli` | 공통 엔진 CLI |
| `apps/desktop` | TypeScript UI와 Tauri 명령·capabilities |
| `fixtures`, `scripts` | 기본 프로젝트, 두 Win32 시험 앱 생성, 재현 가능한 검증 명령 |
Rust DTO에서 생성하는 JSON Schema는 구조와 알 수 없는 필드를 검증하는 용도다. 버전·대상·경로·파일 등의 의미 검증은 공통 엔진이 추가 수행한다. 실제 manifest XML은 [MakeAppx의 기본 검증](https://learn.microsoft.com/en-us/windows/msix/package/create-app-package-with-makeappx-tool)을 끄지 않고 검사한다. Tauri 명령은 [AppManifest와 capability 권한](https://v2.tauri.app/security/capabilities/)으로 main 창에 한정한다.
## 검사와 시험 앱
```powershell
# src에서 실행. 기존 폴더는 덮어쓰지 않는다.
.\scripts\New-Fixtures.ps1 -MakeAppx 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\makeappx.exe'
# .work/demo/FixtureAlpha와 FixtureBeta의 프로젝트를 앱에서 열 수 있다.
.\scripts\Check.ps1 -MakeAppx 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\makeappx.exe'
```
검사 스크립트는 `npm ci`로 의존성을 재설치하므로 먼저 실행 중인 Vite/Tauri 개발 서버를 종료한다. Windows에서 실행 중인 esbuild 파일은 교체할 수 없다.
`cargo test`는 일반 단위/통합 테스트를 수행한다. SDK 통합 테스트는 기본적으로 ignored이며 `MSIX_TEST_MAKEAPPX`를 지정하고 `--ignored`로 명시 실행한다. 이 테스트는 두 identity, 실제 MakeAppx pack/unpack, 원본 변경 후 계획 거부, 중복 빌드, 내보내기 무결성 및 취소를 확인한다. 설치 시험으로 취급하지 않는다.
프로젝트 닫기와 최근 목록 저장 테스트는 `cargo test --workspace`에 포함된다. 시작 화면의 날짜 그룹·검색·HTML 이스케이프와 미저장 변경 처리 테스트는 `apps/desktop`에서 `npm.cmd test`로 실행한다. `scripts/Check.ps1`도 두 검사를 모두 수행한다.
[2026-10-19 검증 기록](tests/verification-2026-10-19.json)에 실제 도구 해시, 시험 결과, 로컬 생성물 해시와 미실행 항목을 보관한다. GUI 컴파일은 통과했지만 현재 자동화 연결을 사용할 수 없어 화면 클릭·시각 검사는 `notRun`이다.
## 현재 구현 범위와 다음 작업
- 구현: workspace/공통 엔진/CLI/GUI, v2 열기·저장·백업·revision 충돌, 파일 포함/제외·정렬·충돌·위험 경로/reparse 차단, PE 아키텍처/PNG 확인, XML 이스케이프, 계획 고정, OS 잠금, Windows Job Object, 도구 타임아웃·취소, MSIX pack/unpack 및 identity/파일 해시 대조, 덮어쓰기 없는 개발 결과와 보고서 내보내기.
- 다음: 운영 서명 프로필·타임스탬프, App Installer, 승인 SDK 버전 정책, 다중 대상 일괄 작업, 작업 이력/재연결 화면, 파일 목록 서버 페이지 처리, 접근성/DPI 전체 시험, Windows VM 설치·실행·업데이트·제거·사용자 데이터 보존 증빙.
- EXE/MSI/MSP, Store, bundle, extension은 명시적으로 거부한다. 운영 서명 프로필/App Installer 요청도 구현 완료 전에는 차단한다.
- 로컬 검증을 통과한 아키텍처/OS 값은 지원 인증을 의미하지 않는다. 도구 해시는 계획 시 고정하지만 승인된 SDK 목록/도구 버전 메타데이터 정책은 아직 없다.
- 프로젝트와 local JSON은 각각 원자적으로 저장하고 이전 파일을 `.bak`으로 보관한다. 두 파일을 한 번에 교체하는 트랜잭션은 아니다. 작업 로그에는 로컬 경로가 포함되므로 결과 공유 시 검토한다.
- 결과는 애플리케이션에서 덮어쓰지 않지만 OS 파일 권한으로 불변성을 강제하지는 않는다. 내보내기 전 MSIX 해시를 다시 검증한다.
백로그 BL-09/10/11/14/16/29/47/48/49/50의 일부를 구현한 상태다. 서명·VM 증빙을 포함한 원래 인수 기준이 남아 있으므로 해당 백로그와 MS6를 완료로 표시하지 않는다.
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "package-builder"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
anyhow.workspace = true
serde.workspace = true
serde_json.workspace = true
schemars.workspace = true
package-domain.workspace = true
package-engine.workspace = true
package-infra.workspace = true
clap = { version = "4", features = ["derive"] }
ctrlc = "3"
+168
View File
@@ -0,0 +1,168 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use package_infra::{self as infra, MsixBackend};
use std::{
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
#[derive(Parser)]
#[command(
version,
about = "Installer Studio — shared Rust package engine (development builds)"
)]
struct Cli {
#[command(subcommand)]
command: Action,
}
#[derive(Subcommand)]
enum Action {
Init {
#[arg(long)]
project: PathBuf,
},
Validate {
#[arg(long)]
project: PathBuf,
},
Plan {
#[command(subcommand)]
command: PlanAction,
},
Build {
#[arg(long)]
project: PathBuf,
#[arg(long)]
plan_id: String,
},
Job {
#[command(subcommand)]
command: JobAction,
},
Export {
#[arg(long)]
project: PathBuf,
#[arg(long)]
release: String,
#[arg(long)]
output: PathBuf,
},
Schema,
Certificate {
#[arg(long)]
project: PathBuf,
#[arg(long, conflicts_with = "thumbprint")]
create: bool,
#[arg(long)]
thumbprint: Option<String>,
},
}
#[derive(Subcommand)]
enum PlanAction {
Release {
#[arg(long)]
project: PathBuf,
#[arg(long)]
target: String,
},
}
#[derive(Subcommand)]
enum JobAction {
Status {
#[arg(long)]
project: PathBuf,
#[arg(long)]
id: String,
},
}
fn print(value: &impl serde::Serialize) -> Result<()> {
println!("{}", serde_json::to_string_pretty(value)?);
Ok(())
}
fn run(action: Action, cancel: &AtomicBool) -> Result<i32> {
match action {
Action::Init { project } => print(&infra::create_project(&std::path::absolute(project)?)?)?,
Action::Validate { project } => {
let doc = infra::open_project(&project)?;
let report = package_engine::validate(
&MsixBackend,
&doc.project,
&doc.local,
doc.path.parent().unwrap(),
);
print(&report)?;
return Ok(if report.diagnostics.is_empty() { 0 } else { 2 });
}
Action::Plan {
command: PlanAction::Release { project, target },
} => print(&infra::plan_release(&project, &target)?)?,
Action::Build { project, plan_id } => print(&infra::build(&project, &plan_id, cancel)?)?,
Action::Job {
command: JobAction::Status { project, id },
} => print(&infra::job_status(&project, &id)?)?,
Action::Export {
project,
release,
output,
} => print(&infra::export_release(
&project,
&release,
&std::path::absolute(output)?,
)?)?,
Action::Schema => print(&schemars::schema_for!(package_domain::Project))?,
Action::Certificate {
project,
create,
thumbprint,
} => print(&infra::configure_developer_certificate(
&project,
if create {
"create"
} else if thumbprint.is_some() {
"select"
} else {
"list"
},
thumbprint.as_deref(),
cancel,
)?)?,
}
Ok(0)
}
fn main() {
let cli = Cli::parse();
let cancel = Arc::new(AtomicBool::new(false));
let signal = cancel.clone();
if let Err(error) = ctrlc::set_handler(move || signal.store(true, Ordering::Relaxed)) {
eprintln!("{error}");
std::process::exit(4);
}
let code = match run(cli.command, &cancel) {
Ok(code) => code,
Err(error) => {
let message = format!("{error:#}");
let code = if message.starts_with("Cancelled") {
130
} else if message.starts_with("JobLocked") {
6
} else if message.starts_with("Tool") {
4
} else if message.starts_with("ArtifactMismatch")
|| message.starts_with("IdentityMismatch")
{
5
} else {
2
};
eprintln!(
"{}",
serde_json::json!({ "code": code, "message": message })
);
code
}
};
std::process::exit(code);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+3
View File
@@ -0,0 +1,3 @@
<!doctype html>
<html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="icon" type="image/x-icon" href="/favicon.ico"><title>Installer Studio</title></head>
<body><div id="window-chrome"></div><div id="app"></div><script type="module" src="/src/main.ts"></script></body></html>
+1425
View File
File diff suppressed because it is too large. Load diff
+14
View File
@@ -0,0 +1,14 @@
{
"name": "msix-studio-desktop",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 1420 --strictPort",
"build": "tsc --noEmit && vite build",
"test": "node --experimental-strip-types --test tests/*.test.mjs",
"tauri": "tauri"
},
"dependencies": { "@tauri-apps/api": "^2.0.0" },
"devDependencies": { "@tauri-apps/cli": "^2.0.0", "typescript": "^5.9.0", "vite": "^7.0.0" }
}
@@ -0,0 +1,30 @@
# Installer Studio 로고
사용자가 선택한 A안(패키지 큐브와 중앙의 I 모양)을 기반으로 제작했다.
- 원본: `../../app-icon.png`
- 화면용: `installer-studio-mark.png` (256 × 256)
- Windows 및 기타 플랫폼 아이콘: `../../src-tauri/icons/`
- 파비콘: `../favicon.ico`
- 시작 화면: 로고 옆에 Installer는 흰색, Studio는 보라색으로 표시한다. 글자는 확대 시 선명하도록 HTML로 렌더링한다.
- 패키징 대상 제품의 미리보기 아이콘은 제작기 로고와 별도로 유지한다.
## 재생성
`src/apps/desktop`에서 실행한다.
```powershell
npm.cmd exec -- tauri icon app-icon.png --output src-tauri/icons
Copy-Item src-tauri/icons/128x128@2x.png public/branding/installer-studio-mark.png
Copy-Item src-tauri/icons/icon.ico public/favicon.ico
```
## 이미지 제작 기록
2026-10-26, 내장 image_gen 도구 사용. 투명 배경 시안에서 발생한 가장자리 노이즈를 피하기 위해 최종 원본은 시작 화면과 어울리는 짙은 배경으로 제작했다.
최종 생성 파일: `exec-c99b833c-a6ec-4354-af2d-fd6b8631bad5.png`. 프로젝트에서 사용하는 원본은 위의 `app-icon.png`에 보관한다.
최종 프롬프트:
> Use case: logo-brand. Create the production Installer Studio application icon, using the input solely as reference for the exact three panel violet cube with a central capital I negative space. Preserve that geometry and violet colors. IMPORTANT: use a completely opaque uniform solid charcoal #202020 background everywhere outside the three cube panels, also inside all gaps. Remove all speckles, colored dots, white fringes and noise, crisp pristine edges with antialiasing. Exactly three geometric panels, pale lavender top diamond, saturated purple left face, light lavender right face. Center on a square canvas, cube occupies 88 percent, balanced padding. No text, no border, no shadow, no glow, no particles, no texture. Flat professional logo.
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

+25
View File
@@ -0,0 +1,25 @@
[package]
name = "msix-studio"
version.workspace = true
edition.workspace = true
license.workspace = true
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
rfd = "0.15"
serde.workspace = true
serde_json.workspace = true
package-domain.workspace = true
package-engine.workspace = true
package-infra.workspace = true
anyhow.workspace = true
uuid.workspace = true
[dev-dependencies]
tempfile.workspace = true
[features]
custom-protocol = ["tauri/custom-protocol"]
+22
View File
@@ -0,0 +1,22 @@
fn main() {
tauri_build::try_build(tauri_build::Attributes::new().app_manifest(
tauri_build::AppManifest::new().commands(&[
"open_project",
"new_project",
"close_project",
"list_recent_projects",
"open_recent_project",
"remove_recent_project",
"save_project",
"choose_source",
"choose_tool",
"validate_project",
"developer_certificate",
"plan_release",
"start_build",
"cancel_build",
"export_release",
]),
))
.expect("Tauri build setup failed");
}
@@ -0,0 +1,32 @@
{
"identifier": "main-editor",
"description": "Local project editor commands only. No shell or arbitrary filesystem plugin.",
"windows": [
"main"
],
"permissions": [
"core:event:allow-listen",
"core:event:allow-unlisten",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"core:window:allow-is-maximized",
"core:window:allow-start-dragging",
"core:window:allow-close",
"core:window:allow-destroy",
"allow-open-project",
"allow-new-project",
"allow-close-project",
"allow-list-recent-projects",
"allow-open-recent-project",
"allow-remove-recent-project",
"allow-save-project",
"allow-choose-source",
"allow-choose-tool",
"allow-validate-project",
"allow-plan-release",
"allow-start-build",
"allow-cancel-build",
"allow-export-release",
"allow-developer-certificate"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-cancel-build"
description = "Enables the cancel_build command without any pre-configured scope."
commands.allow = ["cancel_build"]
[[permission]]
identifier = "deny-cancel-build"
description = "Denies the cancel_build command without any pre-configured scope."
commands.deny = ["cancel_build"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-choose-source"
description = "Enables the choose_source command without any pre-configured scope."
commands.allow = ["choose_source"]
[[permission]]
identifier = "deny-choose-source"
description = "Denies the choose_source command without any pre-configured scope."
commands.deny = ["choose_source"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-choose-tool"
description = "Enables the choose_tool command without any pre-configured scope."
commands.allow = ["choose_tool"]
[[permission]]
identifier = "deny-choose-tool"
description = "Denies the choose_tool command without any pre-configured scope."
commands.deny = ["choose_tool"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-close-project"
description = "Enables the close_project command without any pre-configured scope."
commands.allow = ["close_project"]
[[permission]]
identifier = "deny-close-project"
description = "Denies the close_project command without any pre-configured scope."
commands.deny = ["close_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-developer-certificate"
description = "Enables the developer_certificate command without any pre-configured scope."
commands.allow = ["developer_certificate"]
[[permission]]
identifier = "deny-developer-certificate"
description = "Denies the developer_certificate command without any pre-configured scope."
commands.deny = ["developer_certificate"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-export-release"
description = "Enables the export_release command without any pre-configured scope."
commands.allow = ["export_release"]
[[permission]]
identifier = "deny-export-release"
description = "Denies the export_release command without any pre-configured scope."
commands.deny = ["export_release"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-list-recent-projects"
description = "Enables the list_recent_projects command without any pre-configured scope."
commands.allow = ["list_recent_projects"]
[[permission]]
identifier = "deny-list-recent-projects"
description = "Denies the list_recent_projects command without any pre-configured scope."
commands.deny = ["list_recent_projects"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-new-project"
description = "Enables the new_project command without any pre-configured scope."
commands.allow = ["new_project"]
[[permission]]
identifier = "deny-new-project"
description = "Denies the new_project command without any pre-configured scope."
commands.deny = ["new_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-open-project"
description = "Enables the open_project command without any pre-configured scope."
commands.allow = ["open_project"]
[[permission]]
identifier = "deny-open-project"
description = "Denies the open_project command without any pre-configured scope."
commands.deny = ["open_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-open-recent-project"
description = "Enables the open_recent_project command without any pre-configured scope."
commands.allow = ["open_recent_project"]
[[permission]]
identifier = "deny-open-recent-project"
description = "Denies the open_recent_project command without any pre-configured scope."
commands.deny = ["open_recent_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-plan-release"
description = "Enables the plan_release command without any pre-configured scope."
commands.allow = ["plan_release"]
[[permission]]
identifier = "deny-plan-release"
description = "Denies the plan_release command without any pre-configured scope."
commands.deny = ["plan_release"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-remove-recent-project"
description = "Enables the remove_recent_project command without any pre-configured scope."
commands.allow = ["remove_recent_project"]
[[permission]]
identifier = "deny-remove-recent-project"
description = "Denies the remove_recent_project command without any pre-configured scope."
commands.deny = ["remove_recent_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-save-project"
description = "Enables the save_project command without any pre-configured scope."
commands.allow = ["save_project"]
[[permission]]
identifier = "deny-save-project"
description = "Denies the save_project command without any pre-configured scope."
commands.deny = ["save_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-start-build"
description = "Enables the start_build command without any pre-configured scope."
commands.allow = ["start_build"]
[[permission]]
identifier = "deny-start-build"
description = "Denies the start_build command without any pre-configured scope."
commands.deny = ["start_build"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-validate-project"
description = "Enables the validate_project command without any pre-configured scope."
commands.allow = ["validate_project"]
[[permission]]
identifier = "deny-validate-project"
description = "Denies the validate_project command without any pre-configured scope."
commands.deny = ["validate_project"]
+360
View File
@@ -0,0 +1,360 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod recent;
use package_domain::Project;
use package_engine::{BuildResult, Plan, Validation};
use package_infra::{self as infra, MsixBackend, ProjectDocument};
use std::sync::{
atomic::{AtomicBool, Ordering},
Mutex,
};
use tauri::{Manager, State};
#[derive(Default)]
struct Session {
document: Mutex<Option<ProjectDocument>>,
busy: AtomicBool,
cancel: AtomicBool,
recent_warning: Mutex<Option<String>>,
}
fn document(session: &Session) -> Result<ProjectDocument, String> {
session
.document
.lock()
.map_err(|e| e.to_string())?
.clone()
.ok_or("프로젝트를 먼저 여세요.".into())
}
fn idle(session: &Session) -> Result<(), String> {
if session.busy.load(Ordering::SeqCst) {
Err("빌드 중에는 프로젝트를 변경할 수 없습니다.".into())
} else {
Ok(())
}
}
fn set_document(session: &Session, doc: ProjectDocument) -> Result<ProjectDocument, String> {
*session.document.lock().map_err(|e| e.to_string())? = Some(doc.clone());
Ok(doc)
}
fn error(value: impl std::fmt::Display) -> String {
value.to_string()
}
fn recent_store(app: &tauri::AppHandle) -> Result<recent::RecentStore, String> {
app.path()
.app_data_dir()
.map(recent::RecentStore)
.map_err(error)
}
fn remember(app: &tauri::AppHandle, session: &Session, doc: &ProjectDocument, opened: bool) {
let result = recent_store(app).and_then(|store| store.remember(doc, opened).map_err(error));
if let Ok(mut warning) = session.recent_warning.lock() {
*warning = result
.err()
.map(|e| format!("최근 프로젝트 목록을 저장하지 못했습니다: {e}"));
}
}
#[derive(serde::Serialize)]
struct RecentList {
projects: Vec<recent::RecentView>,
warning: Option<String>,
}
#[tauri::command]
async fn list_recent_projects(
app: tauri::AppHandle,
session: State<'_, Session>,
) -> Result<RecentList, String> {
let store = recent_store(&app)?;
let projects = tauri::async_runtime::spawn_blocking(move || store.list().map_err(error))
.await
.map_err(error)??;
Ok(RecentList {
projects,
warning: session.recent_warning.lock().map_err(error)?.clone(),
})
}
#[tauri::command]
fn open_recent_project(
id: String,
app: tauri::AppHandle,
session: State<'_, Session>,
) -> Result<ProjectDocument, String> {
idle(&session)?;
let path = recent_store(&app)?.find(&id).map_err(error)?;
let doc = infra::open_project(&path).map_err(|e| {
format!("최근 프로젝트를 열 수 없습니다. 파일 위치와 접근 권한을 확인하세요: {e}")
})?;
let doc = set_document(&session, doc)?;
remember(&app, &session, &doc, true);
Ok(doc)
}
#[tauri::command]
fn remove_recent_project(id: String, app: tauri::AppHandle) -> Result<(), String> {
recent_store(&app)?.remove(&id).map_err(error)
}
fn clear_document(session: &Session) -> Result<(), String> {
idle(session)?;
*session.document.lock().map_err(error)? = None;
session.cancel.store(false, Ordering::Relaxed);
Ok(())
}
#[tauri::command]
fn close_project(session: State<'_, Session>) -> Result<(), String> {
clear_document(&session)
}
#[tauri::command]
async fn open_project(
app: tauri::AppHandle,
window: tauri::WebviewWindow,
session: State<'_, Session>,
) -> Result<Option<ProjectDocument>, String> {
idle(&session)?;
let Some(file) = rfd::AsyncFileDialog::new()
.set_parent(&window)
.set_title("프로젝트 열기")
.add_filter("Installer Studio 프로젝트", &["isproj"])
.pick_file()
.await
else {
return Ok(None);
};
let doc = infra::open_project(file.path()).map_err(error)?;
let doc = set_document(&session, doc)?;
remember(&app, &session, &doc, true);
Ok(Some(doc))
}
#[tauri::command]
async fn new_project(
app: tauri::AppHandle,
window: tauri::WebviewWindow,
session: State<'_, Session>,
) -> Result<Option<ProjectDocument>, String> {
idle(&session)?;
let Some(file) = rfd::AsyncFileDialog::new()
.set_parent(&window)
.set_title("새 프로젝트 만들기")
.set_file_name("InstallerProject.isproj")
.add_filter("Installer Studio 프로젝트", &["isproj"])
.save_file()
.await
else {
return Ok(None);
};
let doc = infra::create_project(file.path()).map_err(error)?;
let doc = set_document(&session, doc)?;
remember(&app, &session, &doc, true);
Ok(Some(doc))
}
#[tauri::command]
fn save_project(
project: Project,
expected_revision: String,
app: tauri::AppHandle,
session: State<'_, Session>,
) -> Result<ProjectDocument, String> {
idle(&session)?;
let mut doc = document(&session)?;
if expected_revision != doc.revision {
return Err("RevisionConflict: 다시 열어 주세요.".into());
}
doc.project = project;
let saved = infra::save_project(&doc).map_err(error)?;
let saved = set_document(&session, saved)?;
remember(&app, &session, &saved, false);
Ok(saved)
}
#[tauri::command]
async fn choose_source(
alias: String,
session: State<'_, Session>,
) -> Result<Option<ProjectDocument>, String> {
idle(&session)?;
let mut doc = document(&session)?;
if !doc.project.sources.iter().any(|s| s.alias == alias) {
return Err("UnknownSource: 먼저 프로젝트를 저장하세요.".into());
}
let Some(folder) = rfd::AsyncFileDialog::new().pick_folder().await else {
return Ok(None);
};
doc.local.sources.insert(
alias,
folder.path().to_str().ok_or("NonUnicodePath")?.into(),
);
set_document(&session, doc).map(Some)
}
#[tauri::command]
async fn choose_tool(session: State<'_, Session>) -> Result<Option<ProjectDocument>, String> {
idle(&session)?;
let mut doc = document(&session)?;
let Some(file) = rfd::AsyncFileDialog::new()
.set_title("Windows SDK MakeAppx.exe 선택")
.add_filter("MakeAppx", &["exe"])
.pick_file()
.await
else {
return Ok(None);
};
doc.local.make_appx = Some(file.path().to_str().ok_or("NonUnicodePath")?.into());
set_document(&session, doc).map(Some)
}
#[tauri::command]
async fn validate_project(session: State<'_, Session>) -> Result<Validation, String> {
let doc = document(&session)?;
tauri::async_runtime::spawn_blocking(move || {
package_engine::validate(
&MsixBackend,
&doc.project,
&doc.local,
doc.path.parent().unwrap(),
)
})
.await
.map_err(error)
}
#[tauri::command]
async fn developer_certificate(
action: String,
thumbprint: Option<String>,
session: State<'_, Session>,
) -> Result<infra::CertificateSettings, String> {
let doc = document(&session)?;
if session.busy.swap(true, Ordering::SeqCst) {
return Err("JobLocked".into());
}
let outcome = tauri::async_runtime::spawn_blocking(move || {
infra::configure_developer_certificate(
&doc.path,
&action,
thumbprint.as_deref(),
&AtomicBool::new(false),
)
.map_err(error)
})
.await;
// Keep project transitions blocked until the updated document is in the session.
let result = match outcome {
Ok(Ok(settings)) => set_document(&session, settings.document.clone()).map(|_| settings),
Ok(Err(error)) => Err(error),
Err(error) => Err(error.to_string()),
};
session.busy.store(false, Ordering::SeqCst);
result
}
#[tauri::command]
async fn plan_release(target: String, session: State<'_, Session>) -> Result<Plan, String> {
idle(&session)?;
let doc = document(&session)?;
tauri::async_runtime::spawn_blocking(move || infra::plan_release(&doc.path, &target))
.await
.map_err(error)?
.map_err(error)
}
#[tauri::command]
async fn start_build(
plan_id: String,
app: tauri::AppHandle,
session: State<'_, Session>,
) -> Result<BuildResult, String> {
let doc = document(&session)?;
if session.busy.swap(true, Ordering::SeqCst) {
return Err("JobLocked".into());
}
session.cancel.store(false, Ordering::Relaxed);
let result = tauri::async_runtime::spawn_blocking(move || {
use tauri::Manager;
let session = app.state::<Session>();
infra::build(&doc.path, &plan_id, &session.cancel).map_err(error)
})
.await
.map_err(error);
session.busy.store(false, Ordering::SeqCst);
result?
}
#[tauri::command]
fn cancel_build(session: State<'_, Session>) {
session.cancel.store(true, Ordering::Relaxed);
}
#[tauri::command]
async fn export_release(
release: String,
session: State<'_, Session>,
) -> Result<Option<String>, String> {
idle(&session)?;
let doc = document(&session)?;
let Some(folder) = rfd::AsyncFileDialog::new()
.set_title("내보내기 상위 폴더 선택")
.pick_folder()
.await
else {
return Ok(None);
};
let dest = folder.path().join(&release);
tauri::async_runtime::spawn_blocking(move || {
infra::export_release(&doc.path, &release, &dest)
.map(|path| Some(path.display().to_string()))
.map_err(error)
})
.await
.map_err(error)?
}
fn main() {
tauri::Builder::default()
.manage(Session::default())
.invoke_handler(tauri::generate_handler![
open_project,
new_project,
close_project,
list_recent_projects,
open_recent_project,
remove_recent_project,
save_project,
choose_source,
choose_tool,
validate_project,
developer_certificate,
plan_release,
start_build,
cancel_build,
export_release
])
.run(tauri::generate_context!())
.expect("Installer Studio failed to start");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn close_clears_the_session_without_changing_the_project() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("project.isproj");
let doc = infra::create_project(&path).unwrap();
let original = std::fs::read(&path).unwrap();
let session = Session::default();
set_document(&session, doc).unwrap();
clear_document(&session).unwrap();
assert!(document(&session).is_err());
assert_eq!(std::fs::read(path).unwrap(), original);
}
#[test]
fn close_is_rejected_while_build_is_running() {
let temp = tempfile::tempdir().unwrap();
let doc = infra::create_project(&temp.path().join("project.isproj")).unwrap();
let session = Session::default();
set_document(&session, doc).unwrap();
session.busy.store(true, Ordering::SeqCst);
assert!(clear_document(&session).is_err());
assert!(document(&session).is_ok());
}
}
Loaded 100 of 145 files, more files were not shown because too many files have changed in this diff. Show more