Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
366c8fe173 | ||
|
|
905e6d4eaa | ||
|
|
7f5540f148 | ||
|
|
aba06b3443 | ||
|
|
8cd38f5ba4 | ||
|
|
916b79e5d6 | ||
|
|
3071e0d1ef | ||
|
|
024f244f52 | ||
|
|
fdbc9a9d1b | ||
|
|
6213fec7d2 | ||
|
|
24b624301c |
No files matched your search
@@ -0,0 +1,47 @@
|
||||
# 공통 클린코드 규칙
|
||||
|
||||
모든 파일에 적용한다. 언어별 규칙은 `rust.md`, `frontend.md`, `tauri-ipc.md`, `docs.md`가 보충한다.
|
||||
|
||||
## 범위와 변경 크기
|
||||
|
||||
- 요청한 범위만 바꾼다. 지나가는 코드의 스타일 정리·리네임·재배치를 섞지 않는다.
|
||||
- 새 기능보다 기존 함수 재사용을 먼저 찾는다. `package-infra`에 이미 있는 `hash_file`, `atomic_json`, `reject_links`, `lock_project`, `appended_path` 같은 헬퍼를 다시 만들지 않는다.
|
||||
- 한 함수는 한 가지 일을 한다. 분기가 3단계를 넘으면 이름 있는 함수로 뺀다.
|
||||
- 파일 하나가 500줄을 넘기 시작하면 모듈 분리를 제안한다(현재 `main.ts`, `store.rs`가 경계선).
|
||||
|
||||
## 이름
|
||||
|
||||
- 코드 식별자는 영어. 사용자에게 보이는 문자열(오류 메시지, UI 텍스트, 문서)은 한국어.
|
||||
- 불리언은 상태를 그대로 읽히게 짓는다: `dirty`, `busy`, `available`, `trusted_people`. `is_`/`has_` 접두사는 Rust 메서드에서만.
|
||||
- 약어를 새로 만들지 않는다. `doc`(ProjectDocument), `esc`(HTML escape)처럼 이미 쓰는 것만 유지한다.
|
||||
- JSON 필드는 camelCase, Rust 필드는 snake_case, 변환은 serde `rename_all`에 맡기고 수동 매핑을 하지 않는다.
|
||||
|
||||
## 오류 처리
|
||||
|
||||
- 오류 메시지 형식: `ErrorCode: 한국어 설명과 조치`. 코드는 PascalCase 한 단어(`RevisionConflict`, `MissingTool`, `InputChanged`). 기존 코드가 있으면 재사용하고, 새 코드는 README 또는 변경 기록에 적는다.
|
||||
- 오류를 삼키지 않는다. 실패를 기본값으로 대체하는 fallback(미서명으로 자동 전환, 빈 설정으로 조용히 진행 등)을 만들지 않는다. 예외는 명시된 두 곳만: 최근 목록 저장 실패는 경고로 표시하고 프로젝트 열기를 막지 않는다, 이전 v2 프로젝트의 누락 `registry`/`signing` 필드는 기본값으로 연다.
|
||||
- 조치 방법을 담는다. 사용자가 무엇을 고쳐야 하는지 한 문장으로.
|
||||
|
||||
## 결정성
|
||||
|
||||
- 파일 목록·맵은 정렬된 자료구조(`BTreeMap`, `BTreeSet`, 정렬된 `Vec`)로 다룬다. 같은 입력은 같은 출력을 내야 한다.
|
||||
- 시각은 UNIX 초(u64)로 저장한다. 표시 형식 변환은 UI에서만.
|
||||
- 바이트 수처럼 JS `number` 정밀도를 넘을 수 있는 값은 IPC 경계에서 10진 문자열로 보낸다.
|
||||
|
||||
## 주석과 문서화
|
||||
|
||||
- 주석은 "왜"만 쓴다. 코드가 말하는 "무엇"을 반복하지 않는다. 주석은 영어 한 문장.
|
||||
- 공개 함수에 문서 주석을 달 때는 계약(입력 제약, 실패 조건, 부작용 없음)을 쓴다.
|
||||
- TODO를 남기지 않는다. 미구현은 명시 오류로 거부하고 README의 "다음 작업"이나 백로그에 적는다.
|
||||
|
||||
## 테스트
|
||||
|
||||
- 동작을 바꾸면 테스트를 함께 바꾼다. 실패 경로(거부, 충돌, 취소, 변조 탐지)를 성공 경로와 같은 비중으로 검사한다.
|
||||
- 테스트 이름은 검증하는 규칙을 문장으로 쓴다: `json_and_duplicate_extensions_are_rejected_even_with_valid_contents`.
|
||||
- 실제 SDK·네트워크·레지스트리·인증서 저장소를 건드리는 테스트는 `#[ignore]` 또는 환경 변수 게이트 뒤에 둔다.
|
||||
- 테스트가 실패하면 그대로 보고한다. 테스트를 약화시켜 통과시키지 않는다.
|
||||
|
||||
## 줄 끝과 인코딩
|
||||
|
||||
- UTF-8. `.gitattributes`에 따라 `*.md`, `*.json`, `*.html`은 LF. Rust/TS는 `text=auto`를 따른다.
|
||||
- 파일 끝에 개행 하나. BOM 없음(현재 `style.css`의 BOM은 기존 상태이며 건드리지 않는다).
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
paths:
|
||||
- "docs/**/*.md"
|
||||
- "README.md"
|
||||
- "src/README.md"
|
||||
- "src/tests/*.json"
|
||||
---
|
||||
|
||||
# 문서 · 기록 규칙
|
||||
|
||||
문서는 한국어, 선언형 종결("~한다", "~이다"). 날짜는 `2026-10-23` 형식. 영어 식별자·명령·파일명은 백틱으로 감싼다.
|
||||
|
||||
## 어떤 변경이 어떤 문서를 바꾸는가
|
||||
|
||||
| 변경 | 반드시 갱신 |
|
||||
| --- | --- |
|
||||
| 사용자 동작·CLI 옵션·종료 코드·오류 코드 | `src/README.md` 해당 절 |
|
||||
| `.isproj`/local JSON 필드 | `docs/code-design/13-isproj-project-format.md`, `docs/code-design/examples/msix.isproj`, `src/fixtures/InstallerProject.isproj` |
|
||||
| 기능 추가·정책 결정 | `docs/changes/YYYY-MM-DD-<slug>.md` 신규 작성, 루트 `README.md` 링크 |
|
||||
| 실제 도구를 돌린 시험 | `src/tests/verification-YYYY-MM-DD-<slug>.json` |
|
||||
| 백로그 항목 진척 | `docs/backlog/README.md` 상태 열 |
|
||||
| 범위 변경(1차/후속 이동) | `docs/code-design/12-msix-first-builder.md`, 백로그, `docs/plan/10-development-schedule.md` |
|
||||
|
||||
## 변경 기록(`docs/changes`) 구성
|
||||
|
||||
1. 제목과 첫 단락: 날짜, 무엇을 왜 바꿨는지, 범위 밖임을 명시할 것.
|
||||
2. `## 사용` 또는 `## 결정`: 사용자 절차 또는 결정 목록.
|
||||
3. `## 모델과 실행`: 프로젝트/로컬/계획/결과 필드와 실행 순서.
|
||||
4. `## 확인 결과`: 실행한 검사와 **실행하지 않은 검사**를 모두 나열. 테스트 개수, 해시, 상태 문자열을 적는다.
|
||||
5. `## 근거`: Microsoft Learn 등 외부 문서 링크.
|
||||
|
||||
## 검증 JSON(`src/tests/verification-*.json`)
|
||||
|
||||
- `date`, `checks`(불리언 맵), 실제 결과 객체(`buildId`, `sha256`, `status`, `signingReport` 등), `installationTest`, `notRun` 목록을 포함한다.
|
||||
- 실행하지 않은 항목은 생략하지 않고 `"notRun"`으로 적는다. GUI 클릭 시험을 하지 않았으면 `"guiClickTest": "notRun"`.
|
||||
- 로컬 경로가 들어가므로 공유 전에 검토한다. 토큰·개인 키·PFX 비밀번호는 절대 넣지 않는다.
|
||||
|
||||
## 금지 표현
|
||||
|
||||
- 실행하지 않은 것을 "확인했다"로 쓰지 않는다. 시안·설계 작성은 구현 완료 증빙이 아니다.
|
||||
- "배포 준비 완료", "설치 검증 완료", "지원 인증"은 Windows VM 설치·업데이트·제거 증빙이 있을 때만.
|
||||
- 백로그 ID를 완료로 표시하려면 해당 행의 완료 기준 전체가 충족돼야 한다. 일부 구현은 README의 "일부 구현" 문장으로 남긴다.
|
||||
|
||||
## 링크
|
||||
|
||||
- 저장소 내부 링크는 상대 경로. 깨진 링크를 만들지 않도록 대상 파일 존재를 확인한다.
|
||||
- 루트 `README.md`는 색인 역할이다. 새 설계·매뉴얼·변경 문서를 만들면 한 줄로 추가한다.
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
paths:
|
||||
- "src/apps/desktop/src/**"
|
||||
- "src/apps/desktop/tests/**"
|
||||
- "src/apps/desktop/index.html"
|
||||
- "src/apps/desktop/package.json"
|
||||
- "src/apps/desktop/tsconfig.json"
|
||||
---
|
||||
|
||||
# 프런트엔드(TypeScript · CSS) 규칙
|
||||
|
||||
프레임워크 없는 TypeScript. 화면은 문자열 템플릿으로 만들고 `root.innerHTML`에 넣은 뒤 `data-action`/`data-field` 속성으로 이벤트를 연결한다. `tsc --noEmit` strict와 `node --test`를 통과해야 한다.
|
||||
|
||||
## 모듈 구조
|
||||
|
||||
- `main.ts`는 상태와 조정(invoke 호출, render, action 분기)만 둔다. 화면 조각과 순수 로직은 별도 모듈로 뺀다: `start-screen.ts`, `registry-editor.ts`, `signing-editor.ts`, `project-transition.ts`, `window-chrome.ts`.
|
||||
- 새 탭·편집기는 `render<Name>(data, disabled): string`을 export하는 모듈로 만들고, `main.ts`에서는 import와 action 분기만 추가한다.
|
||||
- 테스트는 `tests/<module>.test.mjs`에서 `.ts`를 직접 import한다. Node의 `--experimental-strip-types`로 실행되므로 **`enum`, `namespace`, 매개변수 프로퍼티, 데코레이터를 쓰지 않는다**. 타입은 `type` 별칭과 `import type`으로.
|
||||
- 순수 함수(그룹화, 경로 표시, 전환 판단)는 DOM 없이 호출 가능해야 테스트할 수 있다. `document`를 쓰는 코드와 분리한다.
|
||||
|
||||
## 보안 · 렌더링
|
||||
|
||||
- 템플릿에 들어가는 모든 동적 값은 `esc()`/`escape()`를 거친다. 프로젝트 이름, 경로, 오류 메시지, ID도 예외 없다. 테스트는 `<img src=x onerror=...>` 같은 값이 이스케이프되는지 확인한다.
|
||||
- `tauri.conf.json`의 CSP는 `'self'`만 허용한다. 인라인 `<script>`, 인라인 `style=`, 외부 폰트·CDN을 추가하지 않는다.
|
||||
- `innerHTML`을 쓰는 곳은 render 함수 안에서만. 이벤트 핸들러는 `addEventListener`로 붙이고 `onclick=` 속성을 쓰지 않는다.
|
||||
- `any`를 새로 쓰지 않는다. 경로 기반 필드 갱신이 필요하면 `Record<string, unknown>`과 단계별 검사로.
|
||||
|
||||
## 상태와 IPC
|
||||
|
||||
- `invoke<T>('command', args)`의 `T`는 Rust 반환 DTO와 필드 단위로 일치하는 `type`이어야 한다. Rust 쪽 `#[serde(default)] Option<T>`는 TS에서 `field?: T | null`.
|
||||
- `isTauri()`가 false인 미리보기 모드에서는 파일 작업을 시도하지 않고 안내 문구를 보여준다.
|
||||
- 모든 action은 `busy` 플래그로 중복 실행을 막고, `cancel`만 `busy` 중에 허용한다.
|
||||
- 저장 안 된 변경(`dirty`)이 있을 때 프로젝트를 바꾸는 모든 경로는 `allowProjectTransition`을 거친다. 저장 실패 시 전환하지 않는다.
|
||||
- 변경 후에는 `invalidate()`로 `report`/`plan`을 무효화한다. 오래된 검증 결과를 화면에 남기지 않는다.
|
||||
|
||||
## 접근성 · 문구
|
||||
|
||||
- 버튼은 텍스트 또는 `aria-label`을 가진다. 아이콘 전용 버튼(`×`)에는 `aria-label`과 `title`.
|
||||
- 상태 메시지 영역은 `role="status"` 또는 `aria-live="polite"`.
|
||||
- 사용자 문구는 한국어, 조치 중심: "변경 사항을 저장한 뒤 다시 검사하세요." 기술 코드는 문장 뒤에 덧붙인다.
|
||||
- "배포 가능", "설치 완료", "검증됨" 같은 단어는 실제 상태 필드(`deploymentReady`, `installationTest`)가 그렇게 말할 때만 쓴다.
|
||||
|
||||
## CSS
|
||||
|
||||
- 색상은 `theme.css`의 `var(--color-*)`만 사용한다. 새 색이 필요하면 토큰을 먼저 추가한다. hex 하드코딩 금지(`.primary`의 `#fff`는 기존 예외).
|
||||
- 선택자는 클래스 기반. ID 선택자와 `!important` 금지.
|
||||
- 반응형 분기는 기존 `1200px`, `1050px` 중단점을 재사용한다.
|
||||
- `style.css`는 한 줄 압축 형식이 섞여 있다. 새 규칙은 파일 끝에 사람이 읽을 수 있는 형식으로 추가하고, 기존 압축 블록을 재포맷하지 않는다.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
paths:
|
||||
- "src/crates/**/*.rs"
|
||||
- "src/apps/cli/**/*.rs"
|
||||
- "src/apps/desktop/src-tauri/**/*.rs"
|
||||
- "src/Cargo.toml"
|
||||
- "src/**/Cargo.toml"
|
||||
---
|
||||
|
||||
# Rust 규칙
|
||||
|
||||
도구 체인은 `rust-toolchain.toml`의 1.94.0 고정. `cargo fmt` 기본 설정과 `clippy -D warnings`를 통과해야 한다. 별도 `rustfmt.toml`/`clippy.toml`을 추가하지 않는다.
|
||||
|
||||
## 크레이트 경계
|
||||
|
||||
| 크레이트 | 허용 | 금지 |
|
||||
| --- | --- | --- |
|
||||
| `package-domain` | serde DTO, 순수 검증, manifest 문자열 생성 | `std::fs`, `std::process`, `anyhow`, OS API |
|
||||
| `package-engine` | 계약 타입, `PackageBackend` 트레이트, 계획 생성, 해시 유틸 | 파일시스템 직접 접근(트레이트로 위임) |
|
||||
| `package-infra` | 파일·프로세스·잠금·레지스트리·서명·저장 | Tauri·clap 타입 |
|
||||
| `apps/cli`, `src-tauri` | 인자 파싱, 세션 상태, 오류 → 문자열 변환 | 비즈니스 로직, 해시·검증 재구현 |
|
||||
|
||||
새 외부 crate는 `[workspace.dependencies]`에 먼저 추가하고 멤버에서 `.workspace = true`로 참조한다. Windows 전용 API는 `windows-sys`의 feature를 명시적으로 추가하고 `#[cfg(windows)]`로 감싼다. 비Windows 빌드가 컴파일되게 유지한다.
|
||||
|
||||
## DTO
|
||||
|
||||
- 모든 직렬화 타입: `#[derive(Debug, Clone, Serialize, Deserialize)]` + `#[serde(rename_all = "camelCase")]`. 사용자 파일에서 읽는 타입은 `deny_unknown_fields`를 반드시 붙인다. 외부로 내보내기만 하는 타입(`Validation`, `BuildResult`, `JobRecord`)은 생략 가능.
|
||||
- `package-domain`의 단순 구조체는 `model!` 매크로를 쓴다. 기본값·enum·문서 주석이 필요하면 매크로 없이 직접 선언한다.
|
||||
- 기존 v2 파일을 깨지 않고 필드를 추가할 때만 `#[serde(default)]`를 쓴다. 필드 제거는 자동 변환 없이 알 수 없는 필드 오류로 거부한다(`projectId` 선례).
|
||||
- JSON Schema(`schemars::JsonSchema`)는 `Project` 트리에 포함되는 모든 타입에 derive한다. 변경 후 `schemas/package-project.schema.json`을 재생성한다(`project-model-change` 스킬).
|
||||
- `u64` 바이트 수를 IPC로 보내면 안 된다. `ResolvedFile.bytes`처럼 `String`으로.
|
||||
|
||||
## 오류
|
||||
|
||||
- 라이브러리 크레이트는 `anyhow::Result`. `ensure!`, `bail!`, `.context()`/`.with_context()`로 `ErrorCode: 설명` 형식을 만든다.
|
||||
- 경로를 메시지에 넣을 때는 `path.display()`. 민감한 값(지문 전체, 토큰)은 넣지 않는다.
|
||||
- `unwrap()`은 테스트와 불변 조건이 보장된 곳(`path.parent()` 직후의 `.unwrap()`처럼 바로 위에서 확인한 경우)에서만. 그 외는 `.context("MissingParent")?`.
|
||||
- `let _ = child.kill();`처럼 정리 경로에서 결과를 무시할 때만 `let _ =`를 쓴다.
|
||||
|
||||
## 파일·프로세스 안전
|
||||
|
||||
- 쓰기는 항상 `atomic_json`/`atomic_bytes` 경유: 같은 디렉터리 임시 파일 → `sync_all` → `persist`. `fs::write`로 결과 파일을 직접 쓰지 않는다(스테이징 폴더 내부 임시 파일은 예외).
|
||||
- 사용자 경로를 열기 전 `reject_links`. 프로젝트 폴더를 바꾸는 작업은 `lock_project`를 먼저 잡고 `_lock` 바인딩을 함수 끝까지 유지한다.
|
||||
- 외부 도구 실행은 `process::run`만 사용한다. 허용 도구는 절대 경로의 `makeappx.exe`, `signtool.exe`, PowerShell 서명 스크립트. 인자는 `&[&OsStr]` 배열. 셸 문자열 결합 금지. 로그 파일 경로를 반드시 받는다.
|
||||
- 긴 루프(파일 복사, 해시, 역추출 검사)는 반복마다 `cancelled(cancel)?`를 호출한다.
|
||||
- 해시 비교는 계획에 고정된 값과 **실행 전 소스**, **복사 중 스트림**, **역추출 결과** 세 지점에서 한다. 하나를 생략하지 않는다.
|
||||
|
||||
## 테스트
|
||||
|
||||
- 통합 테스트는 `crates/<name>/tests/*.rs`. `tempfile::tempdir()`로 격리하고 `_temp` 바인딩을 유지한다. `fixture()` 헬퍼를 재사용한다.
|
||||
- MakeAppx/SignTool이 필요한 테스트는 `#[ignore]` + `std::env::var("MSIX_TEST_MAKEAPPX")`.
|
||||
- 거부 테스트는 오류 문자열의 `ErrorCode`를 `.contains("UnsupportedProjectExtension")`처럼 확인한다. 메시지 전문을 비교하지 않는다.
|
||||
- 시험 PC의 실제 레지스트리·인증서 저장소·`%APPDATA%`를 바꾸는 테스트를 만들지 않는다. `RecentStore`처럼 경로를 주입받는 구조로 만든다.
|
||||
|
||||
## 스타일
|
||||
|
||||
- `use` 정렬은 rustfmt에 맡긴다. `std::{fs, path::{Path, PathBuf}}` 묶음 형식을 따른다.
|
||||
- 작은 private 헬퍼(`error`, `idle`, `document`)는 파일 상단에 모은다.
|
||||
- 매직 넘버는 이름을 붙인다: `MAX_RECENT`, 타임아웃 `Duration::from_secs(900)`은 상수로 올릴 때 README의 "15분 제한"과 함께 바꾼다.
|
||||
- 문자열 상태(`"ArtifactVerified"`, `"Interrupted"`, `"notRun"`)는 JSON 계약이다. 바꾸면 TS 타입, README, 검증 JSON을 같이 바꾼다.
|
||||
@@ -0,0 +1,38 @@
|
||||
# 보안 · 비밀정보 규칙
|
||||
|
||||
모든 파일에 적용한다.
|
||||
|
||||
## 커밋 금지 목록
|
||||
|
||||
`.gitignore`에 있는 항목을 예외 없이 지킨다: `.mcp.json`(Gitea 토큰 포함), `.env*`, `*.pfx`, `*.p12`, `*.key`, `*.isproj.local.json`, `*.bak`, `output/samples/`, `node_modules/`, `target/`. 이 파일들의 내용을 문서·주석·테스트·로그에 복사하지도 않는다.
|
||||
|
||||
`src/.work/`는 로컬 작업 폴더다(미추적). 그 안의 스크립트·토큰·커밋 메타데이터를 소스나 문서에 인용하지 않는다.
|
||||
|
||||
## 시스템 변경 금지
|
||||
|
||||
제작기와 테스트는 다음을 **절대 자동 수행하지 않는다**. 필요한 경우 사용자가 직접 실행할 스크립트와 안내를 출력한다.
|
||||
|
||||
- 빌드 PC의 실제 레지스트리 수정(`offreg.dll`로 오프라인 hive만 생성)
|
||||
- 인증서 신뢰 저장소 등록(`LocalMachine\TrustedPeople`), 인증서 개인 키 내보내기
|
||||
- MSIX 설치·제거, Windows 개발자 모드 변경
|
||||
- Windows SDK·인증서·런타임 자동 설치
|
||||
- 사용자 프로젝트 파일의 자동 변환·일괄 수정
|
||||
|
||||
## 도구 실행
|
||||
|
||||
- 허용 도구는 절대 경로의 `makeappx.exe`, `signtool.exe`, 저장소 내 PowerShell 서명 스크립트. 파일 이름을 대소문자 무시로 검사하고 실행 전후 SHA-256을 계획 값과 비교한다.
|
||||
- 인자는 배열로 전달한다. 셸(`cmd /c`, `powershell -Command "<문자열 결합>"`)로 사용자 입력을 넘기지 않는다.
|
||||
- 자식 프로세스는 Job Object에 붙여 취소·타임아웃 시 함께 종료한다.
|
||||
|
||||
## 경로
|
||||
|
||||
- 사용자 입력 경로는 `safe_relative`로 검사한다: `..`, 절대 경로, 예약 장치 이름(`CON`, `COM1`), 제어 문자, `\:<>"|?*` 거부.
|
||||
- reparse point(심볼릭 링크·junction)는 `reject_links`로 읽기·쓰기 모두 거부한다.
|
||||
- 소스 폴더가 `.work`, `releases`를 포함하거나 그 아래면 거부한다(출력 재수집 방지).
|
||||
- 작업 로그와 검증 JSON에는 로컬 절대 경로가 남는다. 외부 공유 전에 사용자에게 알린다.
|
||||
|
||||
## 프런트엔드
|
||||
|
||||
- CSP `default-src 'self'` 유지. 외부 리소스 로드 금지.
|
||||
- 모든 동적 HTML은 이스케이프. 이벤트는 `addEventListener`.
|
||||
- Tauri 명령은 ID·선택지만 받고 경로를 받지 않는다. 새 명령마다 `capabilities/main.json`에 개별 허용을 추가하고 와일드카드 권한을 쓰지 않는다.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
paths:
|
||||
- "src/apps/desktop/src-tauri/**"
|
||||
- "src/apps/desktop/src/main.ts"
|
||||
---
|
||||
|
||||
# Tauri IPC · 권한 규칙
|
||||
|
||||
데스크톱은 셸·임의 경로 파일시스템 플러그인을 쓰지 않는다. 모든 파일 접근은 Rust 명령이 열어 둔 현재 프로젝트 세션과 `rfd` 파일 대화상자를 통해서만 한다.
|
||||
|
||||
## 명령 작성
|
||||
|
||||
- 시그니처는 `Result<T, String>`. 내부 오류는 `error()` 헬퍼로 문자열화하고, 사용자가 다음에 할 일을 앞에 붙인다: `format!("최근 프로젝트를 열 수 없습니다. 파일 위치와 접근 권한을 확인하세요: {e}")`.
|
||||
- 세션을 바꾸는 명령(열기·새로 만들기·저장·닫기·소스/도구 선택)은 첫 줄에서 `idle(&session)?`로 빌드 중 변경을 막는다.
|
||||
- 파일 IO·해시·도구 실행은 `async fn` + `tauri::async_runtime::spawn_blocking`으로 UI 스레드를 막지 않는다. 짧은 메모리 작업만 동기 `fn`.
|
||||
- `Mutex` 잠금은 함수 범위를 넘기지 않는다. `document()`로 복제해 쓰고 `set_document()`로 돌려놓는다.
|
||||
- 빌드는 `session.busy`를 잡고 종료 시 반드시 풀어야 한다. 취소는 `session.cancel`을 세팅하고 자식 프로세스 종료를 기다린다.
|
||||
- 프런트엔드가 보낸 경로를 그대로 믿지 않는다. 명령 인자로는 ID(`plan_id`, `thumbprint`, `target`)나 선택지만 받고, 실제 경로는 세션 문서에서 가져온다.
|
||||
|
||||
## 권한 체크리스트 (명령 하나 추가 시)
|
||||
|
||||
1. `main.rs`에 `#[tauri::command] fn <snake_name>` 추가.
|
||||
2. `tauri::generate_handler![...]`에 등록.
|
||||
3. `cargo build -p msix-studio`가 `permissions/autogenerated/<snake_name>.toml`을 생성한다. 이 파일은 직접 편집하지 않는다.
|
||||
4. `capabilities/main.json`의 `permissions`에 `"allow-<kebab-name>"` 추가. 이 단계를 빠뜨리면 런타임에 "not allowed" 오류가 난다.
|
||||
5. `main.ts`에 반환 타입을 선언하고 `invoke<T>('<snake_name>', { ... })` 호출.
|
||||
6. `src/README.md`의 기능 설명 갱신.
|
||||
|
||||
`core:*` 권한은 창 제어(`window-chrome.ts`)에 필요한 항목만 추가한다. `core:default`나 플러그인 전체 허용을 넣지 않는다.
|
||||
|
||||
## 설정
|
||||
|
||||
- `tauri.conf.json`의 `bundle.active`는 false를 유지한다. 제작기 자체 배포는 범위 밖이다.
|
||||
- `decorations: false`이므로 창 제어는 `window-chrome.ts`에 모은다. 새 창 권한은 거기서만 쓴다.
|
||||
- 앱 데이터 경로(`%APPDATA%\dev.networksetup.msixstudio`)에는 최근 목록만 저장한다. 프로젝트 내용·인증서·토큰을 저장하지 않는다.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: add-tauri-command
|
||||
description: Installer Studio 데스크톱에 Tauri IPC 명령을 추가하거나 바꾼다. Rust 명령 작성, generate_handler 등록, 자동 생성 권한, capabilities/main.json 허용, TypeScript invoke 타입, README 갱신까지 한 번에 끝낸다. "명령 추가", "IPC", "프런트에서 호출", "capability", "not allowed 오류"에 사용한다.
|
||||
---
|
||||
|
||||
# Tauri 명령 추가
|
||||
|
||||
파일 위치: `src/apps/desktop/src-tauri/src/main.rs`, `src/apps/desktop/src-tauri/capabilities/main.json`, `src/apps/desktop/src/main.ts`.
|
||||
|
||||
## 설계 먼저 결정
|
||||
|
||||
- 비즈니스 로직은 `package-infra`에 함수로 두고 명령은 그 함수를 호출만 한다. CLI(`apps/cli/src/main.rs`)에서도 같은 함수를 쓸 수 있어야 한다.
|
||||
- 인자로 받을 것은 ID·선택지·사용자 입력값이다. 파일 경로를 프런트엔드에서 받지 않는다. 경로가 필요하면 `rfd` 대화상자 또는 세션 문서에서 얻는다.
|
||||
- 세션을 바꾸는가? → `idle(&session)?`를 첫 줄에.
|
||||
- 파일 IO·해시·외부 도구를 실행하는가? → `async fn` + `spawn_blocking`.
|
||||
- 반환 DTO가 새 타입이면 `#[derive(serde::Serialize)]` + `#[serde(rename_all = "camelCase")]`. 바이트 수는 문자열.
|
||||
|
||||
## 절차
|
||||
|
||||
1. **Rust 명령**
|
||||
|
||||
```rust
|
||||
#[tauri::command]
|
||||
async fn example_action(id: String, session: State<'_, Session>) -> Result<ExampleView, String> {
|
||||
idle(&session)?; // 세션을 바꾸는 경우만
|
||||
let doc = document(&session)?;
|
||||
tauri::async_runtime::spawn_blocking(move || infra::example(&doc, &id).map_err(error))
|
||||
.await
|
||||
.map_err(error)?
|
||||
}
|
||||
```
|
||||
|
||||
2. `main.rs` 하단 `tauri::generate_handler![ ... ]` 목록에 `example_action` 추가.
|
||||
3. `cargo build --locked -p msix-studio --features custom-protocol` 실행. `permissions/autogenerated/example_action.toml`이 생긴다. 이 파일은 수정하지 않는다.
|
||||
4. `capabilities/main.json`의 `permissions` 배열에 `"allow-example-action"` 추가(snake → kebab). 기존 순서를 따라 명령 그룹 근처에 넣는다.
|
||||
5. **TypeScript**: `main.ts`(또는 담당 모듈)에 반환 `type` 선언 후 `await invoke<ExampleView>('example_action', { id })`. 호출은 `action()` 분기 안에서 `busy` 보호를 받게 한다.
|
||||
6. 순수 로직이 생겼으면 별도 모듈로 빼고 `tests/<module>.test.mjs`에 테스트 추가.
|
||||
7. `src/README.md`에서 해당 화면·CLI 절을 갱신한다. 새 오류 코드가 있으면 적는다.
|
||||
8. `check` 스킬로 전체 검사.
|
||||
|
||||
## 자주 나는 오류
|
||||
|
||||
- 런타임 `example_action not allowed by ACL`: 4단계 누락.
|
||||
- `permissions/autogenerated`에 파일이 없음: 2단계 누락 또는 빌드 미실행.
|
||||
- TS에서 `undefined` 필드: Rust `snake_case` ↔ TS `camelCase` 불일치. `rename_all` 확인.
|
||||
- 빌드 중 명령이 실행됨: 1단계 `idle` 누락.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: backlog-status
|
||||
description: docs/backlog/README.md의 BL-xx 항목 상태(미착수·진행 중·검증 중·완료·차단)를 운영 규칙에 맞게 갱신하고 변경 기록·검증 증빙과 연결한다. 완료 기준이 모두 충족되지 않은 항목을 완료로 표시하지 않는다. "백로그 갱신", "BL-", "상태 변경", "마일스톤", "완료 처리"에 사용한다.
|
||||
---
|
||||
|
||||
# 백로그 상태 갱신
|
||||
|
||||
백로그는 `docs/backlog/README.md` 하나로 관리하며 Gitea 이슈를 만들지 않는다. 53개 ID는 유지하고 번호를 재사용·삭제하지 않는다.
|
||||
|
||||
## 상태 규칙 (운영 규칙 절 그대로)
|
||||
|
||||
| 상태 | 조건 |
|
||||
| --- | --- |
|
||||
| 미착수 | 기본값 |
|
||||
| 진행 중 | 코드·문서 작업 시작. 동시에 1개 항목만 |
|
||||
| 검증 중 | 구현은 끝났고 완료 기준의 검사가 남음 |
|
||||
| 완료 | 해당 행의 **완료 기준 전체**가 충족되고 증빙(변경 기록 + 검증 JSON)이 연결됨 |
|
||||
| 차단 | 원인·필요 자료·해제 조건을 같은 행에 기록 |
|
||||
|
||||
"일부 구현"은 상태가 아니다. 일부만 됐으면 `진행 중` 또는 `검증 중`으로 두고 `src/README.md`의 "현재 구현 범위" 문장에서 어떤 부분이 됐는지 적는다(현재 BL-09/10/11/14/16/29/47/48/49/50이 이 상태).
|
||||
|
||||
## 절차
|
||||
|
||||
1. 대상 행을 읽고 **완료 기준** 열을 항목별로 나눈다.
|
||||
2. 각 항목에 대해 증빙을 찾는다: 테스트 이름, 변경 기록 절, 검증 JSON의 check 키. 없는 항목은 미충족이다.
|
||||
3. 상태를 결정한다. 하나라도 미충족이면 완료가 아니다. Windows VM 설치·업데이트·제거 증빙이 요구되는 항목(BL-06, BL-53, MS6)은 VM 기록 없이는 완료가 될 수 없다.
|
||||
4. 행을 수정한다. 상태 뒤에 날짜와 링크를 괄호로 붙인다: `검증 중 (2026-10-23, [서명](../changes/2026-10-23-developer-signing.md))`.
|
||||
5. 선행 조건이 바뀌었으면 의존하는 행의 선행 조건 열도 확인한다. 1차 항목이 후속 항목에 의존하게 만들지 않는다.
|
||||
6. 마일스톤 영향이 있으면 `docs/plan/10-development-schedule.md`의 해당 MS 줄을 갱신한다. 날짜를 앞당기는 결정은 사용자 확인 사항이다.
|
||||
7. `src/README.md` 끝의 백로그 요약 문장을 맞춘다.
|
||||
|
||||
## 금지
|
||||
|
||||
- 시안·설계 문서 작성을 구현 증빙으로 인정.
|
||||
- 완료 기준 문구를 줄여서 완료로 만들기. 기준 변경은 별도 변경 기록과 사용자 승인이 필요하다.
|
||||
- P0/P1 우선순위를 상태 갱신과 함께 조용히 바꾸기.
|
||||
- 범위 이동(1차 ↔ 후속). 이는 `12-msix-first-builder.md`와 일정 문서를 함께 바꾸는 별도 결정이다.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: change-record
|
||||
description: 기능·정책 변경을 마친 뒤 docs/changes/YYYY-MM-DD-<slug>.md 변경 기록과 src/tests/verification-YYYY-MM-DD-<slug>.json 검증 기록을 저장소 관례대로 작성하고 README·백로그 링크를 연결한다. "변경 기록", "문서화해", "검증 기록", "changes 작성"에 사용한다.
|
||||
---
|
||||
|
||||
# 변경 기록 작성
|
||||
|
||||
기존 예: `docs/changes/2026-10-23-developer-signing.md`, `src/tests/verification-2026-10-23-developer-signing.json`. 먼저 가장 최근 기록 하나를 읽고 같은 어조와 구조를 따른다.
|
||||
|
||||
## 수집할 사실
|
||||
|
||||
기록을 쓰기 전에 실제로 확인한다. 기억이나 추정으로 채우지 않는다.
|
||||
|
||||
- `cargo test --locked --workspace` 통과 개수, `npm.cmd test` 통과 개수.
|
||||
- 실제 도구를 실행했다면 결과 JSON(`releases/<id>/release.json`, `signing-report.json`)의 `buildId`, `status`, `sha256`, `signing`, `installationTest`, `deploymentReady`.
|
||||
- 실행하지 않은 것: GUI 클릭 시험, Windows VM 설치, 대용량 샘플 재생성 등. 이것도 기록 대상이다.
|
||||
- 바뀐 오류 코드·CLI 옵션·종료 코드.
|
||||
|
||||
## 변경 기록 (`docs/changes/<date>-<slug>.md`)
|
||||
|
||||
```markdown
|
||||
# <기능 이름>
|
||||
|
||||
<YYYY-MM-DD>. <무엇을 어디에 추가/변경했는지 두 문장>. <이 변경이 의미하지 않는 것 한 문장>.
|
||||
|
||||
## 사용 ← 사용자 절차가 있으면. 정책 변경이면 "## 결정"
|
||||
1. ...
|
||||
|
||||
## 모델과 실행
|
||||
- 프로젝트: `field = a | b`; 기존 파일 기본값.
|
||||
- 로컬: ...
|
||||
- 계획: 무엇을 고정하고 언제 재검사하는지.
|
||||
- 빌드: 실행 순서.
|
||||
- 결과: 상태 문자열, 보고서 파일.
|
||||
- 범위 밖: 이번에 하지 않은 것.
|
||||
|
||||
## 확인 결과
|
||||
- Rust 테스트 N개 통과. 프런트엔드 테스트 N개 통과; TypeScript/Vite 빌드 통과.
|
||||
- <실제 도구 시험 결과: 상태, 해시 코드>
|
||||
- <실행하지 않은 검사>를 수행하지 않았다.
|
||||
|
||||
증빙: [시험 결과 JSON](../../src/tests/verification-<date>-<slug>.json)
|
||||
|
||||
## 근거
|
||||
- [Microsoft Learn 등 외부 문서](URL)
|
||||
```
|
||||
|
||||
## 검증 JSON (`src/tests/verification-<date>-<slug>.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"date": "YYYY-MM-DD",
|
||||
"checks": { "<camelCaseCheckName>": true },
|
||||
"builds": { "<label>": { "buildId": "", "status": "", "artifact": "", "sha256": "", "signing": "", "installationTest": "notRun", "deploymentReady": false } },
|
||||
"tests": { "cargoTestWorkspace": "passed (N)", "npmTest": "passed (N)", "makeAppxIntegration": "notRun" },
|
||||
"installationTest": "notRun",
|
||||
"notRun": ["guiClickTest", "windowsVmInstall"]
|
||||
}
|
||||
```
|
||||
|
||||
- 실제 도구를 돌리지 않았으면 `builds`를 생략하고 `notRun`에 적는다.
|
||||
- `checks` 값은 실제 확인한 것만 `true`. 확인하지 않은 항목은 넣지 않거나 `"notRun"`.
|
||||
- 로컬 절대 경로가 포함되면 사용자에게 공유 전 검토를 안내한다. 토큰·개인 키·PFX 비밀번호 금지.
|
||||
- LF 줄 끝, 2칸 들여쓰기, 끝 개행.
|
||||
|
||||
## 연결
|
||||
|
||||
1. 루트 `README.md`의 적절한 목록에 한 줄 추가(설계 문서면 "현재 설계와 시안", 그 외 "문서").
|
||||
2. `src/README.md`에서 바뀐 사용자 절차·CLI·오류 코드 절을 갱신하고 변경 기록으로 링크.
|
||||
3. 백로그 항목이 진척됐으면 `backlog-status` 스킬.
|
||||
4. 관련 설계 문서(`docs/code-design/NN-*.md`)에 결정 날짜와 한 단락을 추가. 설계 문서를 다시 쓰지 않는다.
|
||||
|
||||
## 어조
|
||||
|
||||
선언형 종결. 수식어 없이 사실만. "성공적으로", "완벽히", "안전하게" 같은 평가 단어 금지. 실행하지 않은 것을 숨기지 않는다.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: check
|
||||
description: Installer Studio 저장소의 전체 검사(fmt, clippy, Rust 테스트, 프런트엔드 테스트·빌드, 데스크톱 빌드)를 scripts/Check.ps1과 같은 순서로 실행하고 결과를 보고한다. 커밋 전, PR 전, "검사해", "테스트 돌려", "빌드 확인"에 사용한다.
|
||||
---
|
||||
|
||||
# 전체 검사
|
||||
|
||||
`src/scripts/Check.ps1`과 같은 순서를 따른다. 스크립트를 그대로 실행해도 되지만, 어느 단계에서 실패했는지 보고하려면 단계별로 실행한다. 모든 명령은 `D:\networkSetup\src`에서 실행한다.
|
||||
|
||||
## 사전 조건
|
||||
|
||||
1. 실행 중인 `vite`/`tauri dev`/`msix-studio.exe` 프로세스가 있으면 먼저 종료한다. `npm ci`가 esbuild 파일을 교체하지 못하고, debug 빌드가 실행 파일을 덮어쓰지 못한다.
|
||||
2. 변경 범위를 보고 빠른 모드와 전체 모드를 정한다.
|
||||
- Rust만 바뀜: 1~3단계 + 5단계.
|
||||
- 프런트엔드만 바뀜: 4단계.
|
||||
- `src-tauri`, 공용 DTO, IPC 계약이 바뀜: 전체.
|
||||
|
||||
## 단계
|
||||
|
||||
```powershell
|
||||
# 1. 포맷
|
||||
cargo fmt --all --check
|
||||
# 2. 린트 (경고도 실패)
|
||||
cargo clippy --locked --workspace --all-targets -- -D warnings
|
||||
# 3. Rust 단위·통합 테스트 (SDK 통합 테스트는 ignored)
|
||||
cargo test --locked --workspace
|
||||
# 4. 프런트엔드
|
||||
cd apps/desktop
|
||||
npm.cmd ci --no-audit --no-fund
|
||||
npm.cmd test
|
||||
npm.cmd run build
|
||||
cd ../..
|
||||
# 5. 데스크톱 실행 파일
|
||||
cargo build --locked -p msix-studio --features custom-protocol
|
||||
```
|
||||
|
||||
MakeAppx 통합 시험까지 요청받았으면 `msix-integration-test` 스킬을 따른다.
|
||||
|
||||
## 실패 처리
|
||||
|
||||
- `cargo fmt --check` 실패: `cargo fmt --all`을 실행해 고치고 다시 검사한다. 포맷 변경만 커밋 범위에 들어가게 한다.
|
||||
- clippy 경고: `#[allow]`로 덮지 않는다. 코드를 고친다. 정말 의도된 패턴이면 이유를 주석으로 적고 `allow`를 가장 좁은 범위에 붙인다.
|
||||
- 테스트 실패: 테스트를 약화시키지 않는다. 실패 출력 전문을 보고에 포함한다.
|
||||
- `--locked` 실패: `Cargo.lock`/`package-lock.json`이 변경돼야 한다는 뜻이다. 의존성을 의도적으로 바꾼 경우에만 잠금 파일 갱신을 커밋한다.
|
||||
|
||||
## 보고 형식
|
||||
|
||||
단계별로 통과/실패를 표로 적고, 실행하지 않은 단계는 "실행 안 함"으로 적는다. 실패한 단계는 오류 출력을 코드 블록으로 붙인다. "모두 통과"는 다섯 단계를 실제로 모두 실행했을 때만 쓴다.
|
||||
|
||||
| 단계 | 결과 |
|
||||
| --- | --- |
|
||||
| fmt | 통과 |
|
||||
| clippy | 통과 |
|
||||
| cargo test | 통과 (N개) |
|
||||
| npm test / build | 통과 (N개) |
|
||||
| msix-studio build | 실행 안 함 (프런트엔드만 변경) |
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: msix-integration-test
|
||||
description: Windows SDK MakeAppx.exe를 찾아 MSIX_TEST_MAKEAPPX로 지정하고 package-infra의 ignored 통합 테스트(실제 pack/unpack, 계획 거부, 중복 빌드, 내보내기, 취소)를 실행한 뒤 결과를 해석·기록한다. 선택적으로 FixtureAlpha/Beta 시험 앱을 생성한다. "통합 테스트", "MakeAppx", "실제 빌드 시험", "SDK 테스트", "--ignored"에 사용한다.
|
||||
---
|
||||
|
||||
# MakeAppx 통합 시험
|
||||
|
||||
`cargo test --workspace`는 SDK 테스트를 건너뛴다. 실제 MSIX 생성 경로는 이 절차로만 검증된다. 이 시험은 **설치 시험이 아니다**. 결과를 "설치 검증"이라고 쓰지 않는다.
|
||||
|
||||
## 1. MakeAppx 찾기
|
||||
|
||||
```powershell
|
||||
Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin\*\x64\makeappx.exe' |
|
||||
Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName
|
||||
```
|
||||
|
||||
- 없으면 중단하고 사용자에게 Windows SDK 설치가 필요하다고 알린다. 자동 설치하지 않는다.
|
||||
- 여러 버전이 있으면 가장 높은 버전을 쓰되, 사용한 경로와 SHA-256을 기록한다:
|
||||
|
||||
```powershell
|
||||
(Get-FileHash -Algorithm SHA256 '<경로>').Hash.ToLower()
|
||||
```
|
||||
|
||||
- 서명 테스트는 같은 폴더의 `signtool.exe`를 자동으로 쓴다. 없으면 서명 관련 테스트가 실패하므로 결과 해석 시 구분한다.
|
||||
|
||||
## 2. 실행
|
||||
|
||||
`D:\networkSetup\src`에서. 실행 중인 `msix-studio.exe`/`tauri dev`는 종료한다.
|
||||
|
||||
```powershell
|
||||
$env:MSIX_TEST_MAKEAPPX = '<makeappx.exe 절대 경로>'
|
||||
cargo test --locked -p package-infra --test workflow -- --ignored --nocapture
|
||||
Remove-Item Env:MSIX_TEST_MAKEAPPX
|
||||
```
|
||||
|
||||
또는 전체 검사와 함께: `.\scripts\Check.ps1 -MakeAppx '<경로>'`.
|
||||
|
||||
시험 앱이 필요하면(GUI에서 열어 볼 fixture):
|
||||
|
||||
```powershell
|
||||
.\scripts\New-Fixtures.ps1 -MakeAppx '<경로>' # 기존 .work/demo 폴더는 덮어쓰지 않음
|
||||
```
|
||||
|
||||
## 3. 결과 해석
|
||||
|
||||
통합 테스트가 확인하는 것: 두 identity의 pack/unpack, 원본 변경 후 계획 거부(`InputChanged`), 같은 계획 중복 실행 거부, 내보내기 해시 무결성, 취소 시 자식 프로세스 종료.
|
||||
|
||||
| 증상 | 원인 |
|
||||
| --- | --- |
|
||||
| `MissingTool` | 경로 오타 또는 환경 변수 미설정 |
|
||||
| `UnsupportedTool` | 파일 이름이 `makeappx.exe`가 아님 |
|
||||
| `ToolFailed ... pack.log` | manifest 또는 자산 문제. 로그 파일 경로를 열어 MakeAppx 메시지를 읽는다 |
|
||||
| `ToolTimeout` | 15분 초과. 안티바이러스 실시간 검사 또는 디스크 문제 |
|
||||
| `IdentityMismatch`, `ArtifactMismatch` | 역추출 결과가 계획과 다름. 엔진 버그 가능성, 재현 후 보고 |
|
||||
| 서명 테스트만 실패 | `signtool.exe` 부재 또는 인증서 저장소 상태. 테스트 PC의 저장소를 바꾸지 않는다 |
|
||||
|
||||
## 4. 기록
|
||||
|
||||
- `change-record` 스킬의 검증 JSON에 `makeAppx: { path, sha256 }`, 통과한 테스트 이름, 실패/미실행 항목을 적는다.
|
||||
- `installationTest`는 `"notRun"`, `deploymentReady`는 `false`.
|
||||
- 생성된 샘플이 `output/samples/` 아래면 커밋되지 않는다. 다른 위치에 만들었으면 추적 여부를 확인한다.
|
||||
|
||||
## 금지
|
||||
|
||||
- `Install-TestCertificate.ps1` 실행, `Add-AppxPackage`, 인증서 저장소 변경.
|
||||
- 실패한 테스트를 `#[ignore]`로 바꾸거나 단언을 약화.
|
||||
- 결과를 "두 제품 MSIX 설치/업데이트/제거 검증 완료"(BL-53)로 기록.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: project-model-change
|
||||
description: .isproj(JSON v2) 프로젝트 모델이나 local 설정 모델을 바꿀 때 package-domain DTO, 검증, manifest, JSON Schema 파일, TypeScript 타입, fixture·예제 파일, 설계 문서 13번을 빠짐없이 동기화한다. "필드 추가", "프로젝트 형식", "schema", "DTO 변경", "local.json 필드"에 사용한다.
|
||||
---
|
||||
|
||||
# 프로젝트 모델 변경
|
||||
|
||||
`Project`(`.isproj`)와 `LocalConfig`(`.isproj.local.json`)는 `src/crates/package-domain/src/lib.rs`에 정의되며 `deny_unknown_fields`로 엄격하게 읽는다. 호환 정책: **필드 추가는 `#[serde(default)]`로 기존 파일을 열 수 있게, 필드 제거는 자동 변환 없이 거부.** `schemaVersion`은 2를 유지한다.
|
||||
|
||||
## 먼저 판단할 것
|
||||
|
||||
- 이 값이 **공유 가능한 제품 설정**(→ `Project`)인가, **이 PC에만 있는 경로·인증서**(→ `LocalConfig`)인가. 절대 경로, 지문, SDK 위치는 local로.
|
||||
- 비밀(개인 키, 비밀번호, 토큰)이면 어디에도 저장하지 않는다.
|
||||
- 1차 MSIX 범위 밖(EXE, Store, bundle)이면 필드를 추가하지 않고 거부 오류를 유지한다.
|
||||
|
||||
## 동기화 체크리스트
|
||||
|
||||
1. **DTO** — `lib.rs`. 단순 구조체는 `model!`, 기본값이 필요하면 직접 선언 + `#[serde(default)]`. `JsonSchema` derive 유지.
|
||||
2. **검증** — `package_domain::validate()`에 `check(valid, "Code", "field.path", "메시지")` 추가. 오류 코드는 PascalCase 한 단어. 의미 검증(경로 안전성·버전 형식·대상)도 여기.
|
||||
3. **manifest** — `package_domain::manifest()`가 영향을 받으면 XML 이스케이프를 거쳐 출력. `inspect_manifest`(infra)에서 역추출 검증도 추가.
|
||||
4. **계획 고정** — 빌드 결과에 영향을 주는 값이면 `Plan`에 포함되어 자동 고정된다(`project` 전체가 들어감). local 값이면 `Plan`에 명시 필드 추가 + `pack()` 전 재검사.
|
||||
5. **JSON Schema 파일** — `src/`에서:
|
||||
|
||||
```powershell
|
||||
cargo run -q -p package-builder -- schema | Out-File -Encoding utf8 schemas/package-project.schema.json
|
||||
```
|
||||
|
||||
생성 후 `git diff schemas/`로 의도한 변경만 있는지 확인한다. 줄 끝은 LF.
|
||||
|
||||
6. **TypeScript 타입** — `src/apps/desktop/src/main.ts`의 `Project`/`Document` type. 선택 필드는 `field?: T | null`.
|
||||
7. **UI** — 편집 가능한 값이면 해당 탭의 `field()`/`select()` 추가. JSON 편집기만으로 다루는 값이면 README에 그렇게 적는다.
|
||||
8. **fixture·예제** — `src/fixtures/InstallerProject.isproj`, `docs/code-design/examples/msix.isproj`, `src/scripts/New-Fixtures.ps1`, `src/crates/package-infra/tests/workflow.rs`의 `fixture()`. `create_project()`의 기본값도 확인.
|
||||
9. **테스트** — `project_format.rs`에 (a) 필드 없는 기존 파일이 열림, (b) 라운드트립 저장, (c) 잘못된 값 거부 세 가지. 제거한 필드는 "unknown field" 거부 테스트.
|
||||
10. **문서** — `docs/code-design/13-isproj-project-format.md`(형식), `02-project-model.md`(모델 표), `src/README.md`(사용자 절차). `change-record` 스킬로 변경 기록.
|
||||
11. `check` 스킬.
|
||||
|
||||
## 금지
|
||||
|
||||
- `#[serde(alias)]`나 마이그레이션 코드로 옛 이름을 조용히 받아들이기.
|
||||
- `deny_unknown_fields` 제거.
|
||||
- 스키마 파일을 손으로 편집하기.
|
||||
- `schemaVersion`을 올리기(사용자 결정 필요).
|
||||
@@ -6,6 +6,10 @@
|
||||
*.pfx
|
||||
*.p12
|
||||
*.key
|
||||
*.isproj.local.json
|
||||
*.isproj.bak
|
||||
*.isproj.local.json.bak
|
||||
*.isver.bak
|
||||
|
||||
# Build dependencies and temporary files
|
||||
node_modules/
|
||||
@@ -15,3 +19,8 @@ __pycache__/
|
||||
*.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Local sample payloads, build workspaces and generated MSIX packages
|
||||
/output/samples/
|
||||
|
||||
.mcp.json
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-vscode.cpptools",
|
||||
"rust-lang.rust-analyzer"
|
||||
]
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"rust-analyzer.linkedProjects": ["src/Cargo.toml"],
|
||||
"files.watcherExclude": {
|
||||
"**/target/**": true,
|
||||
"**/node_modules/**": true,
|
||||
"**/.work/**": true
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
# Installer Studio — 작업 지침
|
||||
|
||||
Windows 설치 패키지(현재 MSIX) 제작기. Rust 공통 엔진 + Tauri 2 데스크톱 GUI + CLI. 모든 소스는 `src/`에 있고 설계·계획·변경 기록은 `docs/`에 있다. `output/`의 HTML은 디자인 시안이며 실제 동작 코드가 아니다.
|
||||
|
||||
핵심 제품 원칙 (코드와 문서 모두에 적용):
|
||||
|
||||
- **빌드 성공 ≠ 배포 가능.** `deploymentReady`는 false, `installationTest`는 `notRun`을 유지한다. 실행하지 않은 검사를 통과했다고 쓰지 않는다.
|
||||
- **입력을 바꾸지 않는다.** 소스 파일·빌드 PC 레지스트리·인증서 저장소·사용자 프로젝트 파일을 자동 변환·설치·등록하지 않는다. 결과는 덮어쓰지 않는다.
|
||||
- **거부가 기본.** 알 수 없는 필드, `.isproj` 외 확장자, 상대 경로 도구, reparse point, `.work`/`releases` 아래 소스는 설명과 함께 거부한다. 자동 교정·대체(fallback)를 하지 않는다.
|
||||
- **해시로 증명한다.** 계획에 고정한 소스·도구·인증서 해시를 실행 전과 역추출 후 다시 검증한다.
|
||||
- 1차 범위는 MSIX만. EXE/MSI/MSP, Store 제출, bundle, 운영 서명 프로필 요청은 명시 오류로 거부한다.
|
||||
|
||||
## 구조와 의존 방향
|
||||
|
||||
```
|
||||
package-domain → package-engine → package-infra → apps/cli, apps/desktop/src-tauri
|
||||
(DTO·검증·manifest) (계약·포트·계획) (파일·해시·잠금·도구 실행) (진입점, 얇게 유지)
|
||||
```
|
||||
|
||||
- 역방향 의존 금지. OS·파일시스템·프로세스 접근은 `package-infra`에만 둔다. `package-domain`은 순수 함수와 serde DTO만.
|
||||
- GUI와 CLI는 같은 `package-infra` 함수를 호출한다. 한쪽에만 있는 비즈니스 로직을 만들지 않는다.
|
||||
- 프런트엔드는 프레임워크 없는 TypeScript + 문자열 템플릿 렌더링이다. React 등을 도입하지 않는다.
|
||||
|
||||
## 자주 쓰는 명령 (모두 `src/`에서)
|
||||
|
||||
```powershell
|
||||
cargo fmt --all --check
|
||||
cargo clippy --locked --workspace --all-targets -- -D warnings
|
||||
cargo test --locked --workspace
|
||||
cd apps/desktop; npm.cmd ci; npm.cmd test; npm.cmd run build; cd ../..
|
||||
cargo build --locked -p msix-studio --features custom-protocol
|
||||
.\scripts\Check.ps1 [-MakeAppx '<makeappx.exe 절대 경로>'] # 위 전체를 순서대로 실행
|
||||
```
|
||||
|
||||
- `npm.cmd ci`는 실행 중인 Vite/Tauri 개발 서버를 먼저 종료해야 한다(esbuild 파일 잠금).
|
||||
- SDK 통합 테스트는 `#[ignore]`이며 `MSIX_TEST_MAKEAPPX`를 지정하고 `--ignored`로 명시 실행한다.
|
||||
- 종료 코드: 0 성공, 2 입력/계획 오류, 4 도구 오류, 5 아티팩트/identity 오류, 6 잠금 충돌, 130 취소.
|
||||
|
||||
## 코딩 룰과 스킬
|
||||
|
||||
세부 규칙은 `.claude/rules/`에 있으며 경로별로 자동 적용된다. 작업 유형별 절차는 `.claude/skills/`를 사용한다.
|
||||
|
||||
| 스킬 | 용도 |
|
||||
| --- | --- |
|
||||
| `check` | 커밋 전 전체 검사와 결과 보고 |
|
||||
| `add-tauri-command` | Tauri IPC 명령 추가(Rust → 권한 → capabilities → TS) |
|
||||
| `project-model-change` | `.isproj` v2 DTO·스키마·TS 타입·예제 동기화 |
|
||||
| `change-record` | `docs/changes` 변경 기록과 `src/tests/verification-*.json` 작성 |
|
||||
| `msix-integration-test` | MakeAppx 실제 통합 시험 실행과 기록 |
|
||||
| `backlog-status` | 백로그 상태 갱신 규칙 |
|
||||
|
||||
## 커밋
|
||||
|
||||
- 메시지는 `type: subject` 영문 소문자. type은 `feat`, `fix`, `refactor`, `docs`, `test`, `chore`.
|
||||
- 커밋 전 `check` 스킬 통과. `.mcp.json`, `*.pfx`, `*.isproj.local.json`, `output/samples/`은 절대 커밋하지 않는다.
|
||||
- 사용자가 요청할 때만 커밋한다.
|
||||
@@ -1,15 +1,42 @@
|
||||
# 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 준비, 심사와 단계별 배포.
|
||||
- [초보자를 위한 Azure Artifact Signing 운영 서명 매뉴얼](docs/manuals/azure-artifact-signing/README.md): Azure 계정·조직 신원 확인·인증서 프로필, SignTool로 MSIX 서명, Key Vault 대안.
|
||||
- [설치 및 배포 계획](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)
|
||||
- [EXE 설치 UI — WebView2 BA와 기본 화면](docs/code-design/14-exe-installer-ui.md)
|
||||
- [EXE 버전과 누적 패치](docs/code-design/15-exe-versions-and-patches.md)
|
||||
- [스마트 앱 컨트롤 검사 대상과 서명 범위](docs/code-design/16-smart-app-control-signing-scope.md)
|
||||
- [2026-11-09 EXE 설치 UI 설계 결정](docs/changes/2026-11-09-exe-installer-ui-design.md)
|
||||
- [2026-11-09 EXE 버전별 메타파일](docs/changes/2026-11-09-exe-version-files.md)
|
||||
- [2026-11-09 EXE 패치 버전](docs/changes/2026-11-09-exe-patch-versions.md)
|
||||
- [2026-11-09 EXE 설치화면 편집](docs/changes/2026-11-09-exe-installer-screens.md)
|
||||
- [2026-11-09 설치파일 탐색기형 트리](docs/changes/2026-11-09-exe-file-tree.md)
|
||||
- [2026-11-10 설치파일의 Windows 기본 폴더 위치](docs/changes/2026-11-10-exe-target-roots.md)
|
||||
- [2026-11-11 설치파일 트리의 파일·폴더 삭제](docs/changes/2026-11-11-exe-file-tree-remove.md)
|
||||
- [초기 설치기 시안](output/egbim-installer-preview.html): 비교 자료로 보존합니다.
|
||||
|
||||
현재는 계획·설계와 인터랙티브 시안 단계입니다. 시안은 실제 설치나 시스템 변경을 수행하지 않습니다.
|
||||
`src`의 프로그램은 실제 Windows SDK를 사용해 MSIX를 생성합니다. `output`의 HTML은 별도 디자인 시안이며 실제 설치나 시스템 변경을 수행하지 않습니다. 개발용 패키지 생성·검증 성공은 일반 배포 및 대상 PC 설치 시험 완료를 의미하지 않습니다.
|
||||
|
||||
## 로컬 설정
|
||||
|
||||
|
||||
@@ -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 적용과 접근성 보조기기 시험은 후속 백로그다. 시안의 예시 상태를 실제 설치 검증 증빙으로 사용하지 않는다.
|
||||
@@ -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)
|
||||
@@ -0,0 +1,26 @@
|
||||
# 설치파일 탐색기형 트리
|
||||
|
||||
2026-11-09. 설치 구성 › 설치파일 탭을 설치 폴더 아래의 폴더와 파일을 함께 보여 주는 탐색기형 트리로 바꾸고, 오른쪽 위에 **폴더 추가**·**파일 추가** 버튼을 두었다. 파일 추가는 소스 폴더 안의 파일을 골라 정확한 이름 규칙으로 저장하며, 트리의 파일 목록은 저장하지 않아도 소스 폴더를 읽어 표시한다. 트리 표시는 미리보기이며 빌드 계획의 해시 고정이나 설치 시험을 뜻하지 않는다.
|
||||
|
||||
## 사용
|
||||
|
||||
1. 설치 구성에서 버전을 열고 **설치파일** 탭을 연다. 새 첫 버전은 빈 트리, 복사로 만든 버전은 복사한 버전과 같은 파일이 보인다.
|
||||
2. 트리에서 폴더를 고른다. 고른 폴더를 다시 누르면 접히고 펼쳐진다. 위쪽 "추가할 위치"가 새 폴더·파일이 들어갈 Windows 경로다.
|
||||
3. **폴더 추가**: 팝업에 이름을 입력하고 **만들기**. 빈 폴더는 파일을 넣어야 프로젝트에 저장된다.
|
||||
4. **파일 추가**: 파일 선택 창(첫 소스 폴더에서 시작)에서 여러 파일을 고른다. 연결된 소스 폴더 밖의 파일은 `OutsideSource`로 거부하며 새 소스로 등록하지 않는다.
|
||||
5. 폴더 전체(`**/*`)나 제외 패턴(`**/*.pdb`)은 트리 아래 **파일 규칙(고급)**을 펼쳐 편집한다.
|
||||
|
||||
## 모델과 실행
|
||||
|
||||
- 프로젝트: `FileRule.sourceFolder`(선택, 기본 빈 문자열 = 소스 루트)를 추가했다. 패턴은 이 폴더에서 시작하고, 일치한 파일의 이 폴더 기준 경로가 `targetRelativePath` 아래에 만들어진다. 비어 있으면 파일에 쓰지 않으므로 기존 `.isproj`·`.isver`와 빌드 계획 해시는 그대로다. 안전한 상대 경로가 아니면 `UnsafePath`(`*.sourceFolder`), 없는 폴더는 `MissingSourceFolder`로 거부한다.
|
||||
- 파일 추가: 고른 파일을 (소스, 소스 안 폴더)별로 묶어 `sourceFolder`가 그 폴더이고 `include`가 파일 이름인 규칙에 넣는다. 이름의 `[ ] * ? { }`는 문자 클래스로 감싸 그대로 일치시킨다. 같은 폴더에 같은 파일을 다시 고르면 추가하지 않는다. 여러 소스가 겹치면 가장 깊은 소스를 쓴다.
|
||||
- 미리보기: 새 Tauri 명령 `preview_files(rules)`가 화면의 저장 전 규칙을 받아 세션의 로컬 소스 경로로 파일을 나열한다. 해시를 계산하지 않고, 읽지 못한 규칙은 오류 문장으로 따로 돌려주며 나머지 규칙의 파일은 계속 보인다. 규칙이나 소스 연결이 바뀔 때만 다시 읽고, 입력 중에는 화면을 다시 그리지 않는다.
|
||||
- 파일 선택: 새 Tauri 명령 `choose_files()`가 파일 대화상자를 열고 `(소스 alias, 소스 안 경로)` 목록을 돌려준다. 명령은 경로 인자를 받지 않는다.
|
||||
- 패치 버전: `sourceFolder`를 바꾸면 설치 경로가 바뀌므로 `PatchMovesFiles`로 거부한다.
|
||||
- 범위 밖: 트리에서 개별 파일 삭제, 끌어 놓기, 폴더 이름 바꾸기. 빌드·입력 검사는 기존처럼 해시를 계산하는 `resolve`/`resolve_version`을 쓴다.
|
||||
|
||||
## 확인 결과
|
||||
|
||||
- Rust 테스트 70개 통과(무시 1개: SDK 통합). 새 통합 테스트 `file_preview.rs` 4개: 기준 폴더 규칙이 파일을 선택 폴더 바로 아래에 두고 대괄호 이름을 그대로 일치시킴, 깨진 규칙(UnsafePath·MissingSourceFolder·MissingRequiredFile·TargetPathCollision·MissingSource)을 보고하고 나머지를 계속 표시, 입력 검사도 같은 경로로 해석하고 `sourceFolder` 경로 오류 거부, 고른 파일을 가장 깊은 소스로 매핑하고 소스 밖·없는 파일·소스 미연결 거부.
|
||||
- 프런트엔드 테스트 76개 통과; TypeScript/Vite 빌드와 `cargo build -p msix-studio --features custom-protocol` 통과. 트리 표시·펼침, 빈 트리 문구, 버튼 위치, 읽는 중·오류 표시, 파일 추가 규칙 묶음·중복 방지·이스케이프, 미리보기 요청 중복 방지와 오래된 응답 무시, 폴더 팝업 이스케이프를 검사한다.
|
||||
- 실제 앱에서 파일 선택 창을 열어 고르는 GUI 시험과 대용량 소스(EG-BIM) 스캔 시간 측정은 수행하지 않았다.
|
||||
@@ -0,0 +1,28 @@
|
||||
# EXE 설치화면 편집
|
||||
|
||||
2026-11-09. 설치 구성에 **설치화면** 탭을 추가해 버전마다 설치 단계(추가·삭제·순서·숨김), 화면 문구와 모양, 설치 중 실행할 스크립트를 편집한다. 이번 변경은 모델·화면·입력 검사까지이며 `installer-ui.json`·MSI 사용자 지정 동작 생성, BA 화면 표시, 스크립트 실제 실행과 설치 시험은 범위 밖이다. 규칙은 [14 EXE 설치 UI](../code-design/14-exe-installer-ui.md#제작기-설치화면-편집-2026-11-09)를 따른다.
|
||||
|
||||
## 결정
|
||||
|
||||
- 단계 추가: 기본 카탈로그 화면 외에 약관(한 번), 안내 화면, 입력 화면(입력값은 사용자 속성에 저장)을 추가한다. 사용자 HTML 화면은 허용하지 않는다.
|
||||
- 스크립트: 소스 폴더의 PowerShell(.ps1)·CMD(.cmd/.bat)·EXE 파일을 인수 배열로 실행한다. 시점은 설치 파일 복사 전·후, 제거 파일 삭제 전(관리자), 완료 후(사용자).
|
||||
- 저장 위치: 버전 메타파일. 버전을 추가하면 복사하고 잠긴 버전은 바꾸지 않는다.
|
||||
|
||||
## 모델과 실행
|
||||
|
||||
- `ExeVersion.installerUi`(`theme`, `pages`, `scripts`)를 추가했다. 없는 기존 버전 파일은 표준 마법사(`InstallerUi::default`)로 연다. `schemas/exe-version.schema.json`을 다시 생성했다.
|
||||
- `package_domain::validate_installer_ui`가 화면 종류·순서·숨김·필수 화면, 약관·로고 경로, 입력 항목, 스크립트 종류·시점·확장자·실패 처리·제한 시간, 제목·설명·인수의 속성 참조를 검사하고 `validate_version`에서 호출한다. `package_infra::files::resolve_version`이 스크립트 파일(`MissingScriptFile`)과 약관·로고 파일(`MissingAsset`)의 존재를 확인한다. 소스 폴더 확인은 `source_root`로 묶어 파일 규칙과 함께 쓴다.
|
||||
- 새 오류 코드: `InvalidPageId`, `UnsupportedPage`, `DuplicatePage`, `InvalidPageOrder`, `PageCannotBeHidden`, `MissingPageTitle`, `MissingPage`, `MissingLicenseFile`, `UnexpectedPageOption`, `MissingInputFields`, `DuplicateInputProperty`, `MissingLabel`, `UnsupportedControl`, `InvalidChoice`, `InvalidColorScheme`, `InvalidAccentColor`, `InvalidAssetPath`, `InvalidScriptId`, `MissingScriptName`, `UnsupportedScriptStage`, `UnsupportedScriptKind`, `InvalidScriptPath`, `ScriptKindMismatch`, `UnsupportedFailureAction`, `ScriptFailureAfterFinish`, `InvalidScriptTimeout`, `MissingScriptFile`, `MissingAsset`; 화면 동작 오류 `PageCannotBeRemoved`, `MissingInputPage`.
|
||||
- 화면: `installer-ui-editor.ts`(단계 목록, 선택 화면 편집, 모양, 스크립트 그리드). 첫 버전은 프런트엔드의 `defaultInstallerUi`(Rust 기본값과 같은 내용)로 만든다.
|
||||
|
||||
## 확인 결과
|
||||
|
||||
- 실행: `cargo fmt --check`, `cargo clippy -D warnings`, `cargo test --workspace`(65개 통과, SDK 통합 1개 무시), 프런트엔드 `tsc --noEmit`, `npm test`(68개 통과), `npm run build`.
|
||||
- 새로 검사한 항목: 표준 마법사 유효성, 화면·스크립트 추가 후 유효성, 화면 순서·숨김·중복·누락·알 수 없는 종류, 약관·입력 항목·화면 옵션 오류, 스크립트 확장자·소스·경로·실패 처리·제한 시간·속성 참조, 스크립트·로고 파일 존재와 저장 왕복, 단계 추가·이동·삭제 규칙, 입력 항목·스크립트 추가·삭제, 편집 값 변환, 편집 화면 표시·잠금·이스케이프.
|
||||
- 실행하지 않음: GUI 클릭 시험(`guiClickTest: notRun`), `msix-studio` 데스크톱 빌드, `installer-ui.json`·MSI 사용자 지정 동작 생성, BA 화면 표시, 스크립트 실행, Windows VM 설치·제거·패치 시험.
|
||||
|
||||
## 근거
|
||||
|
||||
- [Windows Installer: Custom Action In-Script Execution Options](https://learn.microsoft.com/en-us/windows/win32/msi/custom-action-in-script-execution-options)
|
||||
- [Windows Installer: Deferred Execution Custom Actions](https://learn.microsoft.com/en-us/windows/win32/msi/deferred-execution-custom-actions)
|
||||
- [Windows Installer: Formatted](https://learn.microsoft.com/en-us/windows/win32/msi/formatted)
|
||||
@@ -0,0 +1,29 @@
|
||||
# EXE 설치 UI 설계 결정
|
||||
|
||||
2026-11-09. EXE 설치 파일의 화면을 자유롭게 꾸미는 것이 가장 중요한 요구로 정해져, 설치 UI 구조와 기본 화면 목록을 [14 EXE 설치 UI](../code-design/14-exe-installer-ui.md)로 정리했다. 설계 문서만 변경했으며 BA·웹 UI·Burn 번들 구현과 설치 시험은 범위 밖이다.
|
||||
|
||||
## 결정
|
||||
|
||||
- 설치 패키지는 WiX MSI를 Burn 번들(`setup.exe`)로 감싼다. WiX 버전과 OSMF 이용 조건은 채택 전에 확정한다.
|
||||
- 설치 화면은 C#(.NET Framework 4.8) BA 호스트가 WebView2로 표시하는 HTML/CSS/TypeScript 웹 UI로 만든다.
|
||||
- BA 호스트와 웹 UI는 공통으로 한 번 빌드·서명하고, 제품별 차이는 `installer-ui.json`과 자산으로만 전달한다.
|
||||
- 기본 화면 카탈로그(신규 설치 7개, 유지 관리 2개, 상태 6개)를 제공한다. 1단계는 `welcome`, `license`, `installFolder`, `review`, `progress`, `finish`, `failure`, `cancelled`, `maintenance`, `uninstallConfirm` 10개다.
|
||||
- WebView2가 없으면 같은 BA의 기본 네이티브 화면으로 설치한다. 무인 설치는 WebView2를 쓰지 않는다.
|
||||
- [09 설치 과정과 UI 편집](../code-design/09-install-flow-and-ui.md)의 Rust/Tauri BA 방식과 WixStdBA 테마를 첫 구현으로 삼는 순서는 채택하지 않는다.
|
||||
|
||||
## 모델과 실행
|
||||
|
||||
- 프로젝트 모델의 `formats.exe.installerUi` 필드는 아직 추가하지 않았다. 구현 단계에서 `project-model-change` 절차로 반영한다.
|
||||
- 웹 UI와 호스트는 정해진 메시지(`init`, `detected`, `validate`, `apply`, `progress`, `result` 등)만 주고받으며 호스트가 모든 입력을 다시 검증한다.
|
||||
|
||||
## 확인 결과
|
||||
|
||||
- 실행한 것: 문서 작성과 저장소 내부 링크 연결(09, 10, 설계 색인, 루트 README).
|
||||
- 실행하지 않은 것: BA 구현, WebView2 연결, Burn 번들 빌드, VM 설치·업그레이드·제거·취소 시험, WebView2 미설치 PC 시험, 접근성 시험. 백로그 BL-08 검증 실험으로 수행한다.
|
||||
|
||||
## 근거
|
||||
|
||||
- [FireGiant — Out-of-process bootstrapper applications](https://docs.firegiant.com/wix/whatsnew/oopbas/)
|
||||
- [FireGiant — WixStdBA](https://docs.firegiant.com/wix/tools/burn/wixstdba/)
|
||||
- [FireGiant — Open Source Maintenance Fee](https://docs.firegiant.com/wix/osmf/)
|
||||
- [Microsoft Learn — Distribute your app and the WebView2 Runtime](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution)
|
||||
@@ -0,0 +1,28 @@
|
||||
# EXE 패치 버전
|
||||
|
||||
2026-11-09. EXE 버전을 메이저 버전과 패치 버전으로 나눴다. 패치 버전은 아래쪽 메이저 버전의 ProductCode를 이어받고, 출시하면 전체 설치 파일과 같은 라인의 이전 버전을 한 번에 올리는 누적 패치 파일을 함께 만든다. 이번 변경은 모델·화면·입력 검사까지이며 MSI·MSP·Burn 생성과 설치·패치 시험은 범위 밖이다. 설계는 [15 EXE 버전과 누적 패치](../code-design/15-exe-versions-and-patches.md)를 따른다.
|
||||
|
||||
## 결정
|
||||
|
||||
- 패치 기준: 누적 패치. 1.0.3 패치는 같은 라인의 1.0.0·1.0.1·1.0.2를 모두 1.0.3으로 올리고 이전 패치를 대체한다.
|
||||
- 산출물: 패치 버전은 전체 설치 파일(새 PC에서 바로 그 버전 설치)과 패치 파일 두 가지. 같은 라인의 이전 버전이 설치된 PC는 패치 파일로만 업데이트한다.
|
||||
- 패치는 이전 버전이 설치한 파일·레지스트리·바로가기를 지우거나 옮길 수 없다. 필요하면 메이저 버전을 만든다.
|
||||
|
||||
## 모델과 실행
|
||||
|
||||
- `ExeVersion.updateType`(`major` 기본 / `patch`)을 추가했다. 이 필드가 없는 기존 버전 파일은 메이저 버전으로 연다. `schemas/exe-version.schema.json`을 다시 생성했다.
|
||||
- `package_domain::line_major`, `patch_baselines`, `validate_version_line`을 추가했다. `validate_version_document`가 선택한 버전을 검사할 때 함께 호출한다. 새 오류 코드: `MissingMajorVersion`, `PatchProductCodeMismatch`, `BaselineNotLocked`, `PatchRemovesFiles`, `PatchMovesFiles`, `PatchRemovesRegistry`, `PatchRemovesShortcut`, `ProductCodeReused`, `PatchProductCode`(화면에서 패치 ProductCode 변경 거부).
|
||||
- 화면: 버전 목록에 종류 열, 버전 추가에 종류 선택(새 버전 번호의 Major·Minor가 아래쪽 메이저 버전과 같으면 패치, 아니면 메이저로 자동 선택하며 직접 바꿀 수 있음. 아래에 메이저 버전이 없는 최초 버전은 패치 선택지를 비활성화하고, 파일로 저장된 경우에도 `MissingMajorVersion`으로 거부). 패치는 라인 메이저 버전의 ProductCode를 받는다. 일반 탭은 종류를 표시하고 패치의 ProductCode를 읽기 전용으로 보여 준다.
|
||||
|
||||
## 확인 결과
|
||||
|
||||
- 실행: `cargo fmt --check`, `cargo clippy -D warnings`, `cargo test --workspace`(domain 25, infra project_format 16·workflow 12, 그 외 7 통과, SDK 통합 1개 무시), 프런트엔드 `tsc --noEmit`, `npm test`(53개 통과), `npm run build`.
|
||||
- 새로 검사한 항목: 라인 메이저 버전과 기준 버전 계산, 패치 ProductCode 불일치·기준 미잠금·메이저 없음·메이저 ProductCode 재사용, 파일 이동·레지스트리 주소 변경·바로가기 삭제 거부와 기준 여러 개의 중복 보고 방지, 데이터·대상·새 항목 허용, `validate_document`를 통한 패치 검사와 `updateType` 저장 왕복, 패치 추가 시 ProductCode 상속, 목록 종류 열, 일반 탭 표시.
|
||||
- 실행하지 않음: GUI 클릭 시험(`guiClickTest: notRun`), `msix-studio` 데스크톱 빌드, MSI·MSP·Burn 생성, Windows VM 설치·패치·제거 시험.
|
||||
|
||||
## 근거
|
||||
|
||||
- [Windows Installer: Patching and Upgrades](https://learn.microsoft.com/en-us/windows/win32/msi/patching-and-upgrades)
|
||||
- [Windows Installer: Minor Upgrades](https://learn.microsoft.com/en-us/windows/win32/msi/minor-upgrades)
|
||||
- [Windows Installer: Sequencing Patches](https://learn.microsoft.com/en-us/windows/win32/msi/sequencing-patches)
|
||||
- [WiX: Building patches](https://docs.firegiant.com/wix/tools/patches/)
|
||||
@@ -0,0 +1,40 @@
|
||||
# EXE 버전별 메타파일
|
||||
|
||||
2026-11-09. EXE 프로젝트에서 버전마다 설치파일·레지스트리·바로가기를 따로 관리하도록 프로젝트 형식을 나눴다. `.isproj`는 모든 버전이 공유하는 설정만 두고, 버전마다 `<이름>.versions/<버전>.isver` 메타파일을 둔다. EXE 빌드·서명·업데이트와 버전 삭제는 범위 밖이다. MSIX 프로젝트는 바뀌지 않았다.
|
||||
|
||||
## 결정
|
||||
|
||||
- 메타파일 위치: 프로젝트 옆 `<이름>.versions/<버전>.isver`(JSON). 파일 이름은 버전과 같아야 한다.
|
||||
- 버전마다 가지는 항목: 제품 버전, ProductCode, 잠금 여부, 설치파일 규칙, 레지스트리, 바로가기. 그 외(앱 이름·게시자·아키텍처·UpgradeCode·설치 폴더·설치 범위·속성문자·소스 alias)는 공유한다.
|
||||
- 새 버전: 선택한 버전을 복사하고 새 ProductCode를 받으며 잠금이 풀린 상태로 시작한다.
|
||||
- 잠금: 사용자가 잠그고 해제한다. 잠긴 버전은 내용을 바꿀 수 없다.
|
||||
- 이전 단일 버전 EXE 형식은 자동 변환하지 않고 열기를 거부한다.
|
||||
|
||||
## 모델과 실행
|
||||
|
||||
- `package_domain::Exe`에서 `msiVersion`·`productCode`·`shortcuts`를 제거하고 `ExeVersion`(`version`, `productCode`, `locked`, `files`, `registry`, `shortcuts`)을 추가했다. 스키마는 `schemas/exe-version.schema.json`, CLI `package-builder version-schema`.
|
||||
- 열기는 `.versions` 폴더의 `*.isver`를 읽어 버전 순으로 정렬한다. 파일 이름과 `version`이 다르면 `VersionFileMismatch`로 거부한다.
|
||||
- 저장은 모든 버전을 먼저 검사한 뒤 쓴다. 다른 곳에서 바뀐 파일은 `RevisionConflict`, 잠긴 버전의 내용 변경은 `VersionLocked`, 목록에서 지운 버전은 패치가 사용하는 메이저(`VersionInUse`)가 아니면 저장할 때 `.isver.bak`으로 옮긴다. 잠긴 버전은 화면에서 휴지통을 숨기며, 잠금을 해제하면 삭제할 수 있다(같은 날 버전 삭제 추가로 `VersionDeletionUnsupported` 대체). 바꾼 버전만 `.isver.bak`을 남기고 다시 쓴다.
|
||||
- 검증은 공유 설정과 선택한 버전을 함께 검사한다(`validate`, `validate_version`, 필드 경로 `version.*`). 바로가기 대상은 그 버전의 해석된 설치 파일과 비교한다. GUI `validate_project`와 CLI `validate`는 EXE에서 버전을 받는다(`--version`).
|
||||
- 새 EXE 프로젝트는 버전 없이 시작한다. 설치 구성의 버전 목록은 버전이 없으면 "*기존 버전이 없습니다. 신규버전을 만드세요."를 표시하고, **버전 추가**로 번호를 지정해 빈 내용의 첫 버전을 만든다.
|
||||
- 화면: 설치 구성은 버전 목록(버전·ProductCode·잠금·저장 상태·항목 수)과 버전 추가 양식을 먼저 보여 주고, 버전을 열면 위쪽에 **← 버전 목록**과 잠금 버튼, 아래에 일반 탭(버전·ProductCode·잠금 상태), 설치파일·레지스트리·바로가기 탭은 선택한 버전을 편집한다. 잠긴 버전은 읽기 전용이다. 설치 앱 정보에는 공유 설정만 남았다.
|
||||
|
||||
## 확인 결과
|
||||
|
||||
- 실행: `cargo fmt --check`, `cargo clippy -D warnings`, `cargo test --workspace`(56개 통과, SDK 통합 1개 무시), 프런트엔드 `npm test`(52개 통과)와 `npm run build`.
|
||||
- 새로 검사한 항목: 버전 없는 새 프로젝트와 첫 버전 저장, 빈 버전 목록 안내·버전 목록·열린 버전 머리글 렌더링, 버전 파일 생성·재열기·저장 왕복, 새 버전 추가와 버전 순 정렬, 동시 추가 충돌, 잠금 시 내용 변경·삭제 거부와 해제 후 수정, 파일 이름 불일치 거부, 이전 형식 거부, 버전별 바로가기 대상 검사, 버전 필드 경로, 버전 복사의 독립성, 버전 스키마 계약.
|
||||
- 실행하지 않음: GUI 클릭 시험(`guiClickTest: notRun`), `msix-studio` 데스크톱 빌드, EXE 빌드·설치 시험(미구현).
|
||||
|
||||
## 근거
|
||||
|
||||
- [Windows Installer ProductVersion](https://learn.microsoft.com/en-us/windows/win32/msi/productversion)
|
||||
- [Windows Installer ProductCode](https://learn.microsoft.com/en-us/windows/win32/msi/productcode)
|
||||
- [Windows Installer UpgradeCode](https://learn.microsoft.com/en-us/windows/win32/msi/upgradecode)
|
||||
|
||||
## 후속 변경 (같은 날): 버전 설명과 목록 정리
|
||||
|
||||
- `ExeVersion.description`(`#[serde(default)]`, 빈 문자열)을 추가했다. 제작자 메모이며 1000자를 넘으면 `DescriptionTooLong`(`version.description`)으로 거부한다. 설치 화면에는 쓰지 않는다. 버전을 복사해 추가하면 설명은 비운다. `schemas/exe-version.schema.json`을 다시 생성했다.
|
||||
- 버전 목록 열을 버전·종류·ProductCode·설명으로 줄였다(설치파일 규칙·레지스트리·바로가기 개수 열 제거). 상태 아이콘과 삭제 버튼은 표 오른쪽 바깥에 둔다.
|
||||
- 버전 › 일반 탭의 상태 행을 없애고 설명 입력을 추가했다. 잠금 상태는 위쪽 막대 오른쪽 끝의 **잠금** / **잠금 해제** 버튼으로 표시·변경한다.
|
||||
- 위쪽 **← 버전 목록** 글자 버튼을 버전 번호 왼쪽의 화살표 아이콘 버튼으로 바꿨다. 버전이 열려 있을 때 사이드바의 **설치 구성**을 다시 눌러도 버전 목록으로 돌아간다.
|
||||
- 검사: 설명 저장 왕복, 1000자 경계(문자 수 기준), 복사 시 설명 초기화, 목록 설명 이스케이프, 일반 탭 설명 입력과 잠금 시 비활성화, 머리글 버튼 상태.
|
||||
@@ -0,0 +1,32 @@
|
||||
# 설치파일의 Windows 기본 폴더 위치
|
||||
|
||||
2026-11-10. EXE 버전이 설치 폴더 외에 ProgramData·공용 파일·사용자 AppData 같은 Windows 기본 폴더에도 파일을 설치하도록 파일 규칙에 대상 루트를 추가했다. 설치파일 탭의 폴더 추가 팝업에서 기본 위치를 드롭다운으로 고르거나 `[속성]경로`로 직접 입력한다. 화면·모델·입력 검사까지이며 MSI Directory 테이블 생성과 실제 설치 시험은 범위 밖이다.
|
||||
|
||||
## 사용
|
||||
|
||||
1. 설치파일 탭에서 **폴더 추가**를 누른다.
|
||||
2. **기본 위치**에서 선택한 폴더, 설치 폴더(INSTALLDIR), Windows 기본 폴더 중 하나를 고르고 폴더 이름을 입력한다. 아래에 만들어질 Windows 경로가 미리 보인다.
|
||||
3. **직접 입력…**을 고르면 이름 칸 대신 경로 칸이 나온다. `[CommonAppDataFolder]Baron\Config`처럼 `[속성]하위\경로`로 적으며, 속성 없이 적거나 `[INSTALLDIR]`로 시작하면 설치 폴더 기준이다. 여러 단계 폴더를 한 번에 만들 수 있다.
|
||||
4. 만든 위치는 트리에 별도 루트(예: `ProgramData CommonAppDataFolder`)로 나타나고, 그 아래에서 파일 추가·패턴 규칙을 쓴다.
|
||||
|
||||
2026-11-11 변경: 폴더 추가 팝업의 기본 위치 선택·직접 입력은 없어지고, Windows 기본 폴더는 설치파일 탭 오른쪽 위 **설치 위치 추가**로 트리에 더한다. 하위 폴더는 각 폴더 행의 폴더 추가 버튼으로 만든다([변경 기록](2026-11-11-exe-file-tree-remove.md)).
|
||||
|
||||
## 모델과 실행
|
||||
|
||||
- 프로젝트: `FileRule.targetRoot`(선택, 기본 빈 문자열 = 설치 폴더)를 추가했다. 비어 있으면 파일에 쓰지 않아 기존 파일과 빌드 계획 해시가 바뀌지 않는다.
|
||||
- 허용 루트(`TARGET_ROOTS`): `ProgramFiles64Folder`, `ProgramFilesFolder`, `CommonAppDataFolder`, `AppDataFolder`, `LocalAppDataFolder`. 시스템·Windows 폴더, 여러 제품이 함께 쓰는 공용 파일(`CommonFiles64Folder`, `CommonFilesFolder`)과 글꼴(`FontsFolder`)은 넣지 않았고(2026-11-11 공용 파일·글꼴 제외), 시작 메뉴·바탕화면은 바로가기 탭에서 다룬다. x86 프로젝트는 64비트 폴더를 쓸 수 없다. MSIX는 대상 루트를 쓸 수 없다. 위반은 `UnsupportedTargetRoot`(`*.targetRoot`).
|
||||
- `AppDataFolder`·`LocalAppDataFolder`는 설치한 사용자의 폴더에만 만들어진다(다른 사용자에게는 없음). 화면 설명에 적었다.
|
||||
- 설치 경로 표기: 미리보기·입력 검사의 대상 경로는 설치 폴더 기준 상대 경로이고, 다른 루트는 `[CommonAppDataFolder]/Baron/x.json`처럼 루트를 앞에 붙인다. 같은 상대 경로가 두 루트에 있어도 충돌하지 않는다.
|
||||
- 패치 버전: 대상 루트를 바꾸면 설치 위치가 바뀌므로 `PatchMovesFiles`로 거부한다.
|
||||
- 폴더 이름: `[`로 시작하는 이름은 루트 표기와 헷갈리므로 거부한다.
|
||||
- 범위 밖: MSI Directory/Component 생성, 사용자별 폴더의 다중 사용자 배포(활성 설정·자체 복구), 루트 폴더 자체의 제거 정책.
|
||||
|
||||
## 확인 결과
|
||||
|
||||
- Rust 테스트 72개 통과(무시 1개: SDK 통합). 대상 루트 미리보기 경로와 루트 간 비충돌, 시스템 폴더·x86의 64비트 폴더·MSIX 대상 루트 거부, 대상 루트 없는 규칙의 저장 형식 유지를 `file_preview.rs`에서 검사한다.
|
||||
- 프런트엔드 테스트 79개 통과; TypeScript/Vite 빌드와 `cargo build -p msix-studio --features custom-protocol` 통과. 팝업 선택지(선택 폴더·설치 폴더·허용 루트·직접 입력, x86·MSIX 제한, 이스케이프), 직접 입력 해석과 오류, 트리의 루트별 표시, 파일 추가·패턴 규칙의 대상 루트 설정을 검사한다.
|
||||
- 실제 앱에서 팝업을 조작하는 GUI 시험과 다른 루트로의 설치 시험은 수행하지 않았다.
|
||||
|
||||
## 근거
|
||||
|
||||
- [Windows Installer 속성 참조(시스템 폴더 속성)](https://learn.microsoft.com/en-us/windows/win32/msi/property-reference)
|
||||
@@ -0,0 +1,48 @@
|
||||
# 설치파일 트리의 파일·폴더 삭제
|
||||
|
||||
2026-11-11. 설치 구성 › 설치파일 트리의 파일과 폴더 행에 삭제 버튼을 추가했다. 삭제는 이 버전의 설치 목록에서 빼는 것이며 소스 폴더의 실제 파일은 지우지 않는다. 화면과 규칙 편집까지이며 EXE 빌드·설치 시험은 범위 밖이다.
|
||||
|
||||
## 사용
|
||||
|
||||
1. 트리의 파일이나 폴더 행에 마우스를 올리면(키보드 포커스 포함) 오른쪽에 휴지통 버튼이 나온다. 설치 폴더(INSTALLDIR) 자체에는 없다.
|
||||
2. 파일은 바로 빠진다. 폴더는 "폴더를 삭제할까요?" 팝업에서 경로와 안의 파일 수를 확인한 뒤 **삭제**를 누른다.
|
||||
3. 저장해야 버전 파일에 반영된다. 저장 전에는 저장하지 않고 닫으면 되돌릴 수 있다. 잠긴 버전은 버튼이 비활성화된다.
|
||||
|
||||
## 모델과 실행
|
||||
|
||||
- 파일: 파일 추가로 넣은 파일(규칙의 포함 패턴이 그 파일 이름)은 그 이름을 포함 패턴에서 지우고, 포함 패턴이 비면 규칙도 지운다. `**/*` 같은 패턴으로 들어온 파일은 그 규칙의 제외 패턴에 경로를 문자 그대로(`[ ] * ? { }` 이스케이프) 더한다.
|
||||
- 폴더: 그 폴더나 하위를 대상으로 하는 규칙을 지운다. 위쪽 폴더의 규칙이 그 폴더에 파일을 넣고 있으면 그 규칙의 제외 패턴에 `폴더/**`를 더한다. 추가만 하고 비어 있던 폴더도 함께 지운다. Windows 기본 폴더 루트(예: ProgramData)도 지울 수 있으며, 설치 폴더 쪽 규칙은 다른 루트에 영향을 주지 않는다.
|
||||
- 어느 규칙이 파일을 넣었는지는 파일 목록 미리보기(`preview_files`)의 규칙 ID로 판단한다. 목록을 읽는 중에는 `NotScanned`로 막고, 규칙을 찾지 못하면 `UnknownRule`, 설치 폴더 자체는 `InstallFolderRemoval`로 거부한다.
|
||||
- 패치 버전에서 이전 버전이 설치한 파일을 빼면 기존처럼 입력 검사가 `PatchRemovesFiles`로 거부한다.
|
||||
- 범위 밖: 여러 항목 한꺼번에 삭제, 실행 취소, 드래그로 옮기기.
|
||||
|
||||
## 확인 결과
|
||||
|
||||
- 프런트엔드 테스트 82개 통과; TypeScript/Vite 빌드 통과. 이름 규칙에서 빼기와 빈 규칙 삭제, 패턴 규칙 제외(한 번만, 문자 그대로), 다른 루트의 파일 제외, 폴더 삭제 시 하위 규칙 삭제·위쪽 규칙 제외·대기 폴더 정리, 루트 폴더 삭제, 설치 폴더·읽는 중·모르는 규칙 거부, 버튼 표시·잠금, 확인 팝업 이스케이프를 검사한다.
|
||||
- Rust 코드는 바뀌지 않았다(Rust 테스트 72개 기존 통과 상태).
|
||||
- 실제 앱에서 버튼을 눌러 보는 GUI 시험은 수행하지 않았다.
|
||||
|
||||
## 후속 변경 (같은 날): 설치파일 화면 단순화
|
||||
|
||||
- 설치파일 탭에는 **폴더 추가**·**파일 추가** 버튼과 트리만 남겼다. 트리 아래 **파일 규칙(고급)**(패턴 규칙 카드·패턴 규칙 추가), 제목 줄(설치 폴더 경로), "추가할 위치" 줄, "파일 목록을 읽는 중" 문구를 없앴다. 경고(소스 alias 없음, 읽지 못한 규칙, VFS)는 있을 때만 표시한다.
|
||||
- 소스 폴더 연결 버튼이 고급 영역에만 있었으므로, **파일 추가**를 눌렀을 때 이 PC에 연결된 소스 폴더가 없으면 먼저 소스 폴더 선택 창을 띄운다. 기존 폴더 선택과 같이 연결 전후로 프로젝트를 저장한다. `MissingSource` 안내 문구도 설치파일 탭 기준으로 바꿨다.
|
||||
- 패턴(`**/*`, 제외 패턴)·소스 기준 폴더를 화면에서 고치는 기능과 연결된 소스 폴더를 바꾸는 기능은 화면에서 빠졌다. 규칙을 직접 고치려면 앱을 닫은 상태에서 `.isver`를 수정한다.
|
||||
- 확인: 프런트엔드 테스트 78개 통과, TypeScript/Vite 빌드와 Rust 검사·데스크톱 빌드 통과. GUI 클릭 시험은 수행하지 않았다.
|
||||
|
||||
## 후속 변경 (같은 날): 버튼 위치·선택 기준·소스 연결
|
||||
|
||||
- 트리를 감싼 테두리 박스를 없애고 **폴더 추가**·**파일 추가**를 탭 설명 줄 오른쪽 끝으로 옮겼다.
|
||||
- 폴더뿐 아니라 파일도 선택할 수 있다. 추가 위치는 선택한 폴더, 선택한 파일의 폴더, 선택이 없으면 최상위(설치 폴더)다. 처음에는 아무것도 선택되어 있지 않다. 선택한 파일을 다시 누르거나 트리의 빈 곳을 누르면 선택이 풀린다.
|
||||
- **파일 추가**는 소스 폴더 선택 창 없이 바로 파일 선택 창을 연다. 이 PC에 연결된 소스 폴더가 없으면 고른 파일이 있는 폴더(여러 파일이면 공통 상위 폴더)를 첫 소스의 폴더로 연결한다. 드라이브 루트는 미리보기·빌드가 드라이브 전체를 읽게 되므로 `RootSource`로 거부한다. 연결은 세션에만 바뀌고 저장해야 로컬 설정 파일에 쓴다. 이미 연결된 소스 밖의 파일은 연결된 폴더 경로를 알려 주는 `OutsideSource`로 거부한다.
|
||||
- 확인: Rust 테스트 통과(`common_folder`의 공통 폴더·드라이브 루트 거부, `OutsideSource` 문구 포함), 프런트엔드 테스트 80개 통과, 데스크톱 빌드 통과. GUI 클릭 시험은 수행하지 않았다.
|
||||
|
||||
## 후속 변경 (같은 날): 행별 추가 버튼과 설치 위치 추가
|
||||
|
||||
- 폴더 추가·파일 추가를 각 폴더 행 오른쪽의 아이콘 버튼으로 옮겼다. 추가는 버튼을 누른 그 폴더에 들어간다. 파일 행에는 삭제 버튼만 있다. 행 선택(강조)과 선택 기준 추가는 없앴고, 폴더 행을 누르면 접히거나 펼쳐진다.
|
||||
- 탭 설명 줄 오른쪽 끝에는 **설치 위치 추가**(EXE만)만 둔다. 아직 트리에 없는 Windows 기본 폴더(`TARGET_ROOTS`, x86은 64비트 폴더 제외)를 골라 트리에 루트로 더하며, 모두 추가했으면 비활성화된다. 추가한 루트는 행의 삭제로 다시 뺄 수 있다.
|
||||
- 폴더 추가 팝업은 기본 위치 선택·직접 입력 대신 그 폴더 아래에 만들 이름만 받는다. `Data\Config`처럼 여러 단계를 한 번에 만들 수 있다.
|
||||
- 새 버전은 설치 폴더 행(폴더·파일 추가 버튼 포함)과 "*설치할 파일들을 추가하세요." 문구로 시작한다.
|
||||
- 확인: 프런트엔드 테스트 78개 통과, TypeScript/Vite 빌드 통과. Rust 코드는 바뀌지 않았다. GUI 클릭 시험은 수행하지 않았다.
|
||||
- 루트 행에 전체 설치 경로를 표시하고, 하위 폴더·파일 행은 마우스를 올리면 전체 경로를 보여 준다(프런트엔드 테스트 79개 통과).
|
||||
- 기본 설치 폴더(EXE)도 삭제할 수 있다. 그 안을 대상으로 하는 규칙과 대기 폴더를 지우고 트리에서 숨긴다(다른 루트는 영향 없음). 설치 위치 추가로 다시 넣거나 그 안을 대상으로 하는 항목이 생기면 다시 보인다. 숨김은 화면 상태라 버전을 다시 열면 원래대로 보인다.
|
||||
- 설치 위치 추가 팝업은 기본 설치 폴더와 허용된 Windows 폴더를 모두 나열하고, 이미 트리에 있는 위치는 "추가됨"으로 비활성화한다. **직접 입력…**으로 `[속성]하위\경로`(속성 없으면 기본 설치 폴더 기준)를 받는다. 허용되지 않은 속성은 `UnsupportedTargetRoot`, 이미 있는 위치·폴더는 `DuplicateFolder`로 팝업 안에 표시한다. 프런트엔드 테스트 81개 통과.
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
# 프로젝트와 데이터 모델
|
||||
|
||||
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) 참조.
|
||||
|
||||
2026-11-09 프로젝트 유형 분리: 한 `.isproj`는 `formats.msix`와 `formats.exe` 중 **정확히 하나**를 가진다. 있는 형식이 프로젝트 유형(MSIX/EXE)이며 GUI 시작 화면과 CLI `init --kind`에서 정한다. 둘 다 있거나 둘 다 없는 파일은 `InvalidProjectKind`로 열지 않고, 저장으로 유형을 바꾸면 `ProjectKindChanged`로 거부한다. EXE 대상은 `format: exe`, `delivery: offline-singlefile`, `distribution: direct`만 허용한다. EXE 빌드·서명은 미구현이며 계획 생성에서 `UnsupportedFormat`으로 거부한다.
|
||||
|
||||
2026-11-09 EXE 버전별 메타파일: EXE 프로젝트는 공유 설정과 버전별 내용을 나눈다.
|
||||
|
||||
| 파일 | 내용 |
|
||||
| --- | --- |
|
||||
| `<이름>.isproj` (`formats.exe`) | `architecture`(x64·x86), `upgradeCode`(생성 시 대문자 `{GUID}`, 모든 버전 공유), `installScope`(`perMachine`만), `installRoot`(`[ProgramFiles64Folder]` 또는 x86의 `[ProgramFilesFolder]` + 안전한 상대 경로), `properties`(사용자 속성 `{name, value}`; 기본 속성은 `package_domain::BUILTIN_PROPERTIES`, 해석·치환은 `parse_formatted`·`expand_formatted`). 최상위 `files`·`registry`는 비어 있어야 한다(`VersionedContent`). 템플릿: [`ExeInstallerProject.isproj`](../../src/fixtures/ExeInstallerProject.isproj) |
|
||||
| `<이름>.versions/<버전>.isver` (`ExeVersion`) | `version`(Major.Minor.Patch, 파일 이름과 같아야 함), `description`(제작자 메모, 1000자 이하, 없으면 빈 문자열, 설치 화면에 표시 안 함, `DescriptionTooLong`), `productCode`(대문자 `{GUID}`, UpgradeCode와 달라야 함. 메이저 버전마다 새 값, 패치 버전은 라인 메이저 버전과 같은 값), `updateType`(`major` 기본 / `patch`, [15 EXE 버전과 누적 패치](15-exe-versions-and-patches.md)), `locked`(사용자가 잠금·해제), `files`(파일 규칙), `registry`, `shortcuts`(`{id, location, folder, name, target, arguments}`; `target`은 그 버전의 설치 파일 중 하나, `arguments`는 서식 문자열). 스키마: `schemas/exe-version.schema.json`, 예제: [`exe.1.0.0.isver`](examples/exe.1.0.0.isver) |
|
||||
|
||||
새 EXE 프로젝트는 버전 없이 시작하며, 첫 버전은 사용자가 번호를 지정해 빈 내용으로 만든다. 이후 버전은 선택한 버전을 복사하며, 메이저 버전은 새 ProductCode를 받고 패치 버전은 아래쪽 메이저 버전의 ProductCode를 이어받는다. 검증은 다른 버전과의 관계(`validate_version_line`: ProductCode 재사용, 패치 기준 잠금, 이전 버전 내용 제거·이동 금지)도 검사한다. 저장은 버전 파일마다 읽은 시점의 해시를 비교하고(`RevisionConflict`), 잠긴 버전의 내용 변경(`VersionLocked`)과 패치가 ProductCode를 이어받는 메이저 버전의 삭제(`VersionInUse`)를 거부한다. 화면은 잠긴 버전의 삭제 버튼을 숨기며, 잠금 해제와 삭제를 한 번에 저장하는 것은 허용한다. 바꾼 파일만 `.isver.bak`을 남기고 다시 쓰며, 삭제한 버전 파일은 `.isver.bak`으로 옮긴다. 검증은 공유 설정(`validate`)과 선택한 버전(`validate_version`, 필드 경로 `version.*`)을 함께 검사한다. 이전 단일 버전 EXE 형식(`msiVersion`·`productCode`·`shortcuts`가 `formats.exe`에 있는 파일)은 자동 변환하지 않고 열기를 거부한다. 버전 파일의 `installerUi`(설치 단계·화면 문구·모양·스크립트)는 [14 EXE 설치 UI](14-exe-installer-ui.md#제작기-설치화면-편집-2026-11-09)를 따르며, 없으면 표준 마법사로 연다. 아래 범용 예제의 나머지 EXE 필드(prerequisites, patchPolicy, installationFlow)는 아직 모델에 없으며 알 수 없는 필드로 거부한다.
|
||||
|
||||
**현재 계약은 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 +46,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, sourceFolder(선택, 패턴이 시작하는 소스 안 폴더, 빈 값이면 생략), targetRoot(선택, EXE만, 설치 폴더 대신 쓸 Windows 폴더 속성 — `TARGET_ROOTS`, 빈 값이면 생략), 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 +68,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) 참조.
|
||||
|
||||
## 서로 다른 버전의 구분
|
||||
|
||||
| 값 | 목적 |
|
||||
|
||||
@@ -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 생성 전에 고정하고, 그룹 공개는 별도 배포 시스템에서 수행한다.
|
||||
@@ -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/)
|
||||
|
||||
@@ -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 및 서명 연동 도구를 검사하는 환경 진단 화면을 제공한다. 필요한 외부 도구를 자동 다운로드하거나 번들에 재배포하는 기능은 라이선스·고정 버전 검토 후 추가하며, 최초 구현은 관리자가 준비한 도구 프로필을 사용한다.
|
||||
@@ -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 심사는 수행하지 않았다.
|
||||
@@ -1,7 +1,15 @@
|
||||
# 설치 과정 편집과 설치 UI 커스터마이징
|
||||
|
||||
2026-10-19 실행 범위 변경: 이 문서의 EXE 설치 과정·BA·HTML 페이지 편집은 후속 범위다. 첫 제품의 Tauri UI는 MSIX 제작 설정을 편집하며 설치 마법사를 생성하지 않는다. [현재 설계](12-msix-first-builder.md) 참조.
|
||||
|
||||
2026-11-09 결정: EXE 설치 UI는 C# BA 호스트와 WebView2 웹 UI로 만들고 기본 화면 카탈로그를 제공한다. 아래 "Rust/Tauri 사용자 정의 설치 UI" 절과 "기본 테마 편집 → 사용자 정의 BA" 구현 순서는 [14 EXE 설치 UI](14-exe-installer-ui.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 +101,7 @@ UI는 일반 권한을 유지하고 시스템 변경은 Burn/MSI의 적합한
|
||||
|
||||
## 데이터 계약과 무인 설치
|
||||
|
||||
`PackageProject`에 `installationFlow`와 `installerUi`를 추가한다. 기존 초안 예제에서 생략한 경우 버전이 고정된 기본 과정/테마를 적용하고 빌드 스냅샷에는 전체 정의와 리소스 해시를 저장한다.
|
||||
v2 `PackageProject.formats.exe`에 `installationFlow`와 `installerUi`를 둔다. 기존 초안 예제에서 생략한 경우 버전이 고정된 기본 과정/테마를 적용하고 빌드 스냅샷에는 전체 정의와 리소스 해시를 저장한다.
|
||||
|
||||
| 정의 | 필드 |
|
||||
| --- | --- |
|
||||
@@ -110,7 +118,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,125 @@
|
||||
# 설치 마법사와 편집 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 설계를 유지한다. 설치기 제품을 바꾸거나 기존 제거 동작을 그대로 복제하는 변경이 아니다.
|
||||
|
||||
현재 결과는 상호작용 시안과 구현 설계다. 실제 설치·백업·서명 검사·관리자 승격·재부팅은 실행하지 않으며 표시되는 경로·진행률·검증 결과는 예시다.
|
||||
|
||||
2026-11-09: 기본으로 제공할 화면 ID·숨김 규칙·1단계 화면 10개와 `installer-ui.json` 형식은 [14 EXE 설치 UI](14-exe-installer-ui.md)를 따른다. 아래 `prepare`·`apply`는 14번의 `filesInUse`·`progress`에 대응한다.
|
||||
|
||||
## 참고한 패턴
|
||||
|
||||
| 출처 | 확인한 특징 | 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)을 따른다.
|
||||
@@ -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,79 @@
|
||||
# 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 예제의 내용을 유지하고 확장자만 적용한 설계용 파일이다. 미확정 값이 포함되어 있으므로 그대로 배포용 패키지를 만들 수 있다는 의미는 아니다. [EXE 프로젝트 예제](examples/exe.isproj)는 `upgradeCode`가 `null`인 템플릿이며 새 프로젝트를 만들 때 GUID를 채운다.
|
||||
|
||||
2026-10-27 결정: 별도의 `projectId` 필드를 화면과 저장 모델에서 제거했다. 제작기는 파일 경로로 프로젝트를 열고, MSIX 앱 계열은 `formats.msix.identity.name`과 `publisher`로 식별한다. 파일 이름이나 앱 표시 이름을 바꾸어도 MSIX Identity를 자동 변경하지 않는다. 향후 MSI 식별자도 별도로 관리한다. 새로운 제품으로 복제하는 기능은 별도의 명시적 작업으로 설계한다.
|
||||
|
||||
개발 중인 v2 계약에서 필드를 제거했으며 기존 호환·자동 변환은 제공하지 않는다. `projectId`가 남아 있는 파일은 알 수 없는 필드 오류로 거부한다. 사용자 파일과 기존 빌드 이력을 일괄 수정하지 않는다.
|
||||
|
||||
2026-11-09 결정: 같은 `.isproj` 형식으로 **MSIX 프로젝트**와 **EXE 프로젝트**를 만든다. 별도 `kind` 필드를 두지 않고 `formats`에 있는 형식 하나(`msix` 또는 `exe`)로 유형을 판단한다. 열기는 파일 유형에 맞는 작업 화면으로 이어지며, 유형이 모호한 파일은 열지 않고 저장으로 유형을 바꾸지 않는다. 세부 필드는 [프로젝트 모델](02-project-model.md)을 따른다.
|
||||
|
||||
2026-11-09 추가: 파일 규칙(`files[]`, 버전 파일의 `files[]`)에 선택 필드 `sourceFolder`를 둔다. 패턴이 시작하는 소스 안 폴더이며, 비어 있으면 소스 루트이고 파일에 쓰지 않는다. 그래서 이 필드가 없는 기존 파일은 그대로 열리고 저장해도 바이트가 바뀌지 않는다. 자세한 동작은 [설치파일 탐색기형 트리](../changes/2026-11-09-exe-file-tree.md)를 따른다.
|
||||
|
||||
2026-11-10 추가: 파일 규칙에 선택 필드 `targetRoot`(EXE만)를 둔다. 설치 폴더 대신 `targetRelativePath`가 시작할 Windows 폴더 속성(`CommonAppDataFolder` 등)이며, 비어 있으면 설치 폴더이고 파일에 쓰지 않는다. [설치파일의 Windows 기본 폴더 위치](../changes/2026-11-10-exe-target-roots.md) 참조.
|
||||
|
||||
## 파일과 작업 폴더
|
||||
|
||||
2026-11-09: EXE 프로젝트는 버전마다 `EG-BIM.versions/<버전>.isver` 메타파일을 둔다. 형식은 [프로젝트 모델](02-project-model.md)을 따른다. `.isproj`와 함께 형상 관리하고, 버전 파일의 `.bak`은 제외한다.
|
||||
|
||||
```text
|
||||
project/
|
||||
EG-BIM.isproj # 공유 가능한 제품/패키지 설정
|
||||
EG-BIM.versions/ # EXE 전용: 버전별 설치파일·레지스트리·바로가기·ProductCode
|
||||
1.13.1.isver
|
||||
1.14.0.isver
|
||||
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 구조를 유지하므로 **작업 폴더 하나에 프로젝트 하나**를 둔다. 다른 이름으로 저장은 별도 작업 폴더를 사용하며, 기존 이력 폴더를 자동 복사하거나 다른 프로젝트와 합치지 않는다. 상대 경로는 대상 폴더 기준으로 다시 계산하고, 연결이 깨지는 소스는 저장 전에 안내한다.
|
||||
|
||||
## 생성·열기·저장
|
||||
|
||||
| 동작 | 설계 규칙 |
|
||||
| --- | --- |
|
||||
| 새 프로젝트 | 시작 화면의 새 MSIX/EXE 프로젝트 버튼 또는 CLI `init --kind msix|exe`. 기본 파일명 `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가 바뀌지 않는지 확인한다.
|
||||
- 백업 이름, 외부 변경 충돌, 중간 저장 실패, 상대 경로 및 최근 목록 갱신을 확인한다.
|
||||
- 탐색기 연결은 앱 배포 방식별 등록·제거와 경로 인자 전달을 별도 검증한다.
|
||||
@@ -0,0 +1,245 @@
|
||||
# EXE 설치 UI: WebView2 BA와 기본 화면
|
||||
|
||||
결정일: 2026-11-09. EXE 설치 파일(`setup.exe`)의 화면을 사용자가 자유롭게 꾸밀 수 있게 하는 구조와 기본으로 제공할 화면 목록을 정한다. 이 문서는 설계이며 BA·웹 UI 구현, Burn 연결, Windows 설치 시험은 수행하지 않았다. MSIX는 Windows가 설치 UI를 관리하므로 이 문서의 대상이 아니다.
|
||||
|
||||
[09 설치 과정과 UI 편집](09-install-flow-and-ui.md)의 단계·데이터 계약과 [10 설치 마법사 재설계](10-installer-wizard-redesign.md)의 모드별 흐름·실패 처리 원칙은 유지한다. 아래 결정이 두 문서와 다르면 이 문서를 따른다.
|
||||
|
||||
## 결정
|
||||
|
||||
| 항목 | 결정 |
|
||||
| --- | --- |
|
||||
| 설치 패키지 | WiX로 만든 MSI를 Burn 번들(`setup.exe`)로 감싼다. WiX 버전과 이용 조건(OSMF)은 채택 전에 확정한다. |
|
||||
| 설치 UI | **사용자 정의 Burn BA**를 사용한다. BA 호스트는 C#(.NET Framework 4.8)로 만들고 화면은 **WebView2가 표시하는 HTML/CSS/TypeScript 웹 UI**로 만든다. |
|
||||
| 제품별 차이 | BA 호스트와 웹 UI는 공통으로 한 번 빌드·서명한다. 제품마다 다른 화면 설정은 `installer-ui.json`과 이미지·약관 자산으로만 전달한다. |
|
||||
| 기본 화면 | 아래 [기본 화면 카탈로그](#기본-화면-카탈로그)를 표준으로 제공한다. 제작자는 문구·자산·표시 여부·일부 순서를 편집한다. |
|
||||
| WebView2가 없을 때 | 같은 BA 안의 단순한 네이티브 기본 화면(WPF)으로 설치를 진행한다. 인터넷이 되면 WebView2 웹 부트스트래퍼를 BA 선행 패키지로 먼저 설치할 수 있다. 고정 버전 런타임 포함은 기본값이 아니다. |
|
||||
| 무인 설치 | `/quiet`·`/passive`에서는 WebView2를 만들지 않고 같은 검증과 기본값으로 설치한다. |
|
||||
| 제외 | Rust/Tauri로 BA를 직접 구현하는 방식(09의 "Rust/Tauri 사용자 정의 설치 UI")과 WixStdBA 테마 편집을 첫 구현으로 삼는 순서는 채택하지 않는다. WiX가 C++·.NET BA API만 공식 제공하기 때문이다. |
|
||||
|
||||
## 구조
|
||||
|
||||
```text
|
||||
setup.exe (Burn 번들, 서명)
|
||||
├─ Burn 엔진 ........ 탐지 → 계획 → 적용, UAC 권한 상승, 롤백, 프로그램 추가/제거 등록
|
||||
├─ BA 호스트 (C#, .NET Framework 4.8)
|
||||
│ ├─ WebView2 ── 웹 UI 표시
|
||||
│ ├─ 기본 네이티브 화면 ── WebView2를 쓸 수 없을 때
|
||||
│ └─ 브리지 ── 웹 UI ↔ Burn 메시지 중계, 입력 재검증
|
||||
├─ 웹 UI (Vite 빌드 결과: HTML/CSS/JS)
|
||||
├─ installer-ui.json + 로고·이미지·약관
|
||||
├─ 선행 프로그램 패키지
|
||||
└─ 제품 MSI
|
||||
```
|
||||
|
||||
WiX v5부터 Burn은 BA를 별도 실행 파일로 띄운다. BA 호스트는 WiX가 제공하는 .NET BA 라이브러리로 Burn과 통신한다. 실제 파일·레지스트리 변경은 Burn과 MSI가 수행하며 BA나 웹 UI가 `msiexec`를 따로 실행하지 않는다.
|
||||
|
||||
Installer Studio도 WebView2(Tauri)를 쓰므로 같은 웹 UI에 가짜 브리지를 연결해 제작 화면에서 미리보기를 제공한다. 미리보기는 시스템을 변경하지 않으며, 최종 판정은 실제 번들의 VM 시험으로 한다.
|
||||
|
||||
## 실행 흐름
|
||||
|
||||
1. Burn이 BA 호스트를 실행한다. 대화형 모드면 호스트가 창을 만들고 WebView2 사용 가능 여부를 확인한다.
|
||||
2. 웹 UI는 번들 안의 파일만 가상 호스트(`https://installer.local/`)로 읽는다. 외부 주소 이동, 새 창, 다운로드는 차단한다.
|
||||
3. Burn 탐지 결과(설치 여부, 설치된 버전, 기능 상태, 선행 프로그램 필요 여부)를 웹 UI에 보내고, 웹 UI는 신규 설치·업그레이드·유지 관리 중 맞는 흐름을 연다.
|
||||
4. 사용자가 `review`에서 시작을 누르면 웹 UI가 `apply` 메시지를 보낸다. 호스트는 경로 규칙, 허용 기능, 약관 동의를 다시 검사한 뒤 Burn 변수를 설정하고 계획·적용을 요청한다.
|
||||
5. Burn의 진행·오류·재부팅·취소 이벤트를 웹 UI에 전달한다. 적용이 시작된 뒤에는 선택값과 계획을 바꾸지 않는다.
|
||||
6. 완료 화면의 "앱 실행"은 원래 사용자 권한으로 실행한다.
|
||||
|
||||
## 브리지 메시지
|
||||
|
||||
웹 UI는 아래 형식의 메시지만 보낼 수 있다. 호스트는 알 수 없는 `type`, 형식이 틀린 값, 현재 상태에서 허용되지 않는 요청을 거부한다.
|
||||
|
||||
| 방향 | `type` | 내용 |
|
||||
| --- | --- | --- |
|
||||
| 호스트 → UI | `init` | `installer-ui.json`, 선택 언어, 실행 모드(대화형) |
|
||||
| 호스트 → UI | `detected` | 설치 상태(`absent`/`older`/`same`/`newer`), 설치된 버전·경로, 기능 상태, 필요한 선행 프로그램, 차단 사유 |
|
||||
| 호스트 → UI | `validation` | 입력 검증 결과(필드별 오류 코드와 문구) |
|
||||
| 호스트 → UI | `progress` | 전체 진행률, 현재 패키지·작업 이름 |
|
||||
| 호스트 → UI | `filesInUse` | 사용 중인 프로그램 목록 |
|
||||
| 호스트 → UI | `result` | `succeeded`/`failed`/`cancelled`, 재부팅 필요 여부, 오류 코드, 로그 경로 |
|
||||
| UI → 호스트 | `validate` | 설치 폴더, 기능 선택 등 입력 검증 요청 |
|
||||
| UI → 호스트 | `browseFolder` | 폴더 선택 대화상자 요청. 결과 경로도 호스트가 검증 |
|
||||
| UI → 호스트 | `apply` | 동작(`install`/`modify`/`repair`/`uninstall`), 설치 폴더, 기능, 추가 작업, 약관 동의 |
|
||||
| UI → 호스트 | `cancel` | 취소 요청. 처리 결과는 `result`로 확인 |
|
||||
| UI → 호스트 | `retryFilesInUse` / `closeApplications` | 사용 중인 프로그램 다시 확인. 강제 종료는 기본 동작이 아니다. |
|
||||
| UI → 호스트 | `launchApp`, `openLink`, `restart`, `close` | 완료 후 작업. `openLink`는 `installer-ui.json`에 등록된 주소만 허용 |
|
||||
|
||||
## 기본 화면 카탈로그
|
||||
|
||||
모든 화면은 공통 틀(상단 로고·제품명·단계 표시, 본문, 하단 이전/다음·실행/취소 버튼)을 쓴다. 언어는 한국어·영어를 기본으로 하고 시스템 언어로 자동 선택한다.
|
||||
|
||||
### 신규 설치 흐름
|
||||
|
||||
| 순서 | 화면 ID | 내용 | 숨김 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `welcome` | 제품명·버전·로고, 안내 문구. 이전 버전이 있으면 업그레이드 안내 | 가능 |
|
||||
| 2 | `license` | 약관 전문과 동의. 동의해야 다음으로 진행 | 약관이 없을 때만 |
|
||||
| 3 | `installFolder` | 기본 설치 폴더, 찾아보기, 필요·사용 가능 공간 | 가능. 숨기면 기본 경로 |
|
||||
| 4 | `features` | 기능 트리와 용량. 필수 기능은 해제 불가 | 가능. 숨기면 기본 선택 |
|
||||
| 5 | `tasks` | 바탕화면 바로가기, 파일 연결 등 선택 작업 | 가능. 숨기면 기본값 |
|
||||
| 6 | `review` | 경로, 기능, 선행 프로그램, 필요한 권한 요약과 시작 버튼 | 불가 |
|
||||
| 7 | `progress` | 현재 작업, 전체 진행률, 취소 | 불가 |
|
||||
|
||||
### 유지 관리 흐름
|
||||
|
||||
이미 설치된 PC에서 `setup.exe`를 실행하거나 프로그램 추가/제거에서 변경을 선택할 때 사용한다.
|
||||
|
||||
| 화면 ID | 내용 | 다음 화면 |
|
||||
| --- | --- | --- |
|
||||
| `maintenance` | 수정, 복구, 제거 선택 | 수정: `features` → `review`, 복구·제거: `review` 또는 `uninstallConfirm` |
|
||||
| `uninstallConfirm` | 제거할 항목과 남는 항목(사용자 도면·설정·백업) 안내, 확인 | `progress` |
|
||||
|
||||
### 공통 상태 화면
|
||||
|
||||
조건에 따라 표시하며 숨길 수 없다.
|
||||
|
||||
| 화면 ID | 조건 | 내용 |
|
||||
| --- | --- | --- |
|
||||
| `blocked` | 시작 전 검사 실패 | 더 높은 버전 설치됨, 지원하지 않는 OS, 디스크 부족 등 원인과 조치 |
|
||||
| `filesInUse` | 설치할 파일을 쓰는 프로그램이 있음 | 저장 후 종료 안내, 다시 확인 |
|
||||
| `finish` | 성공 | 결과 요약, 앱 실행 선택, 링크 |
|
||||
| `rebootRequired` | 성공했지만 재부팅 필요 | 이유, 지금/나중에 선택. 기본은 나중에 |
|
||||
| `failure` | 오류 | 실패 단계, 기존 제품 상태, 로그 위치, 다음 조치 |
|
||||
| `cancelled` | 사용자 취소 | 취소 시점과 엔진이 확인한 실제 상태 |
|
||||
|
||||
### 편집 규칙
|
||||
|
||||
- 화면을 숨겨도 그 단계의 작업은 기본값으로 실행한다. 숨김은 표시 여부만 바꾼다.
|
||||
- `license`는 약관이 지정되어 있으면 숨길 수 없다. 숨김을 동의로 처리하지 않는다. 무인 설치의 동의 전달은 별도 인자로 정한다.
|
||||
- 순서 변경은 `welcome`~`tasks` 사이의 선택 화면에만 허용한다. `review` 이후와 상태 화면은 고정한다.
|
||||
- 표시하는 입력은 실제 설치 정의와 연결되어야 한다. 예를 들어 `tasks`의 바로가기 항목은 MSI 바로가기 정의가 있어야 하며, 연결이 없으면 빌드 오류로 처리한다.
|
||||
|
||||
| 대상 | 편집 가능 항목 |
|
||||
| --- | --- |
|
||||
| 모든 화면 | 제목, 설명, 다국어 문구, 로고, 강조 색, 밝기 테마 |
|
||||
| `welcome` | 대표 이미지, 안내 문구 |
|
||||
| `license` | 약관 파일(RTF/TXT/HTML), 동의 문구 |
|
||||
| `installFolder` | 기본 경로(프로젝트 `formats.exe.installRoot`), 경로 변경 허용 여부 |
|
||||
| `features` | 기능 이름·설명·기본 선택·필수 여부 |
|
||||
| `tasks` | 항목 표시, 기본 선택 상태 |
|
||||
| `finish` | 앱 실행 표시와 기본 선택, 링크 목록 |
|
||||
|
||||
## installer-ui.json
|
||||
|
||||
빌드 시 프로젝트 설정에서 생성해 번들에 넣는 파일이다. 제작기에서는 버전 메타파일의 `installerUi`로 편집하며([제작기 설치화면 편집](#제작기-설치화면-편집-2026-11-09)), 이 절은 번들에 넣는 생성 결과의 형식을 정한다. 자산 경로는 프로젝트 폴더 기준의 안전한 상대 경로만 허용한다.
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"template": { "id": "standard-wizard", "version": "1.0.0" },
|
||||
"theme": {
|
||||
"logo": "assets/logo.png",
|
||||
"welcomeImage": "assets/welcome.png",
|
||||
"accentColor": "#7257C5",
|
||||
"colorScheme": "dark"
|
||||
},
|
||||
"locales": { "default": "ko-KR", "available": ["ko-KR", "en-US"] },
|
||||
"strings": {
|
||||
"ko-KR": { "welcome.title": "EG-BIM 설치를 시작합니다" },
|
||||
"en-US": { "welcome.title": "Welcome to EG-BIM Setup" }
|
||||
},
|
||||
"pages": [
|
||||
{ "id": "welcome", "visible": true },
|
||||
{ "id": "license", "visible": true, "file": { "ko-KR": "assets/license.ko.rtf", "en-US": "assets/license.en.rtf" } },
|
||||
{ "id": "installFolder", "visible": true, "allowChange": true },
|
||||
{ "id": "features", "visible": false },
|
||||
{ "id": "tasks", "visible": true },
|
||||
{ "id": "review" },
|
||||
{ "id": "progress" }
|
||||
],
|
||||
"finish": {
|
||||
"launchApp": { "visible": true, "defaultChecked": true },
|
||||
"links": [{ "label": "release.notes", "url": "https://example.com/egbim/release-notes" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 알 수 없는 필드, 카탈로그에 없는 화면 ID, 숨길 수 없는 화면의 `visible: false`, 허용 범위 밖의 순서는 빌드 전에 거부한다.
|
||||
- 상태 화면은 `pages`에 적지 않는다. 문구만 `strings`로 바꾼다.
|
||||
- `url`은 `https`만 허용하고 `openLink` 요청은 이 목록과 비교한다.
|
||||
- 빌드 스냅샷에는 `installer-ui.json`과 모든 자산의 SHA-256을 기록한다. 서명한 `setup.exe`의 화면을 외부 파일로 바꾸지 않는다.
|
||||
|
||||
## 제작기 설치화면 편집 (2026-11-09)
|
||||
|
||||
설치 구성 › **설치화면** 탭에서 버전마다 설치 단계·화면 문구·모양·스크립트를 편집한다. 값은 버전 메타파일(`.isver`)의 `installerUi`에 저장하고, 버전을 추가하면 함께 복사하며, 잠긴 버전은 읽기 전용이다. 이 필드가 없는 기존 버전 파일은 아래 표준 마법사로 연다. 빌드 시 이 값에서 `installer-ui.json`과 MSI 사용자 지정 동작을 만든다(EXE 빌드와 함께 후속 구현).
|
||||
|
||||
| 항목 | 규칙 |
|
||||
| --- | --- |
|
||||
| 표준 마법사 | `welcome`, `installFolder`, `review`, `progress`, `finish` 다음에 `maintenance`, `uninstallConfirm`, `failure`, `cancelled`. 이 9개는 지울 수 없다. |
|
||||
| 단계 추가 | `license`(한 번만, 약관 파일 필수), `info`(안내 화면: 제목·설명), `input`(입력 화면). 추가한 단계는 `review` 바로 앞에 들어가며 삭제할 수 있다. |
|
||||
| 순서 | `welcome`·`license`·`installFolder`·`info`·`input`만 `review` 앞에서 순서를 바꾼다. `review`·`progress`·`finish`와 유지 관리·결과 화면은 고정이다(`InvalidPageOrder`). |
|
||||
| 숨김 | `welcome`·`installFolder`·`info`·`input`만 숨긴다. 숨겨도 그 단계는 기본값으로 진행한다(`PageCannotBeHidden`). |
|
||||
| 문구 | 제목·설명은 서식 문자열이며 `[ProductName]` 같은 속성문자를 쓴다. 정의되지 않은 속성은 `UnknownProperty`. |
|
||||
| 입력 화면 | 항목마다 저장할 사용자 속성(프로젝트 설정 › 속성문자), 이름, 형식(`text`·`checkbox`·`choice`), 기본값, 선택 목록, 필수 여부를 둔다. 같은 속성을 두 항목이 쓰지 않는다(`DuplicateInputProperty`). 입력값은 레지스트리·바로가기·스크립트 인수에서 `[속성]`으로 쓴다. |
|
||||
| 모양 | 밝기(`dark`/`light`), 강조 색(`#RRGGBB`), 로고(프로젝트 폴더 기준 PNG·JPG·SVG). 약관 파일은 RTF·TXT·HTML. 파일이 없으면 `MissingAsset`. |
|
||||
| 설치 폴더 | `fixedFolder`를 켜면 기본 설치 폴더를 바꿀 수 없다. |
|
||||
|
||||
### 스크립트
|
||||
|
||||
| 항목 | 규칙 |
|
||||
| --- | --- |
|
||||
| 종류 | `powershell`(.ps1), `cmd`(.cmd·.bat), `exe`(.exe). 확장자가 다르면 `ScriptKindMismatch`. 화면에 스크립트 본문을 입력하지 않는다. |
|
||||
| 파일 | 프로젝트 소스(alias) 폴더 안의 `/` 경로. 설치 파일과 별도로 번들에 포함하므로 설치되지 않는 파일도 쓸 수 있다. 없으면 `MissingScriptFile`. 빌드 스냅샷에 SHA-256을 기록한다. |
|
||||
| 실행 시점 | `beforeFiles`(설치: 파일 복사 전), `afterFiles`(설치: 파일 복사 후), `beforeUninstall`(제거: 파일 삭제 전)은 MSI 지연 사용자 지정 동작으로 설치 엔진 권한(관리자)으로 실행한다. `afterFinish`(완료 후)는 BA가 완료 화면 뒤에 로그인 사용자 권한으로 실행한다. |
|
||||
| 인수 | 한 줄에 하나씩 배열로 전달하고 명령 문자열로 합치지 않는다. PowerShell은 `powershell.exe -NoProfile -ExecutionPolicy Bypass -File <파일> <인수...>`, CMD는 `cmd.exe /d /c <파일> <인수...>` 형태로 실행하며 인수는 따옴표 규칙에 맞게 하나씩 이스케이프한다. 각 인수는 서식 문자열이다. |
|
||||
| 실패 처리 | `fail`은 0이 아닌 종료 코드·제한 시간 초과에서 설치를 중단하고 설치 엔진의 변경을 되돌린다. 스크립트가 직접 바꾼 내용은 되돌리지 않는다. `ignore`는 로그에 남기고 계속한다. `afterFinish`는 `ignore`만 허용한다(`ScriptFailureAfterFinish`). |
|
||||
| 제한 시간 | 1~3600초(기본 600). 시간이 지나면 프로세스 트리를 종료한다. |
|
||||
| 로그 | 스크립트 이름, 시점, 종료 코드, 소요 시간을 설치 로그에 남긴다. 출력에 비밀 값을 쓰지 않도록 안내한다. |
|
||||
|
||||
패치 버전도 같은 스크립트를 가질 수 있으며 패치 적용 시 실행된다. 스크립트 실행은 Windows VM 설치·제거·패치 시험 전에는 검증된 것으로 표시하지 않는다.
|
||||
|
||||
## WebView2 가용성
|
||||
|
||||
Windows 11과 대부분의 Windows 10에는 WebView2 Evergreen 런타임이 있지만 LTSC, Server, 업데이트가 막힌 오프라인 PC에는 없을 수 있다.
|
||||
|
||||
| 상황 | 동작 |
|
||||
| --- | --- |
|
||||
| WebView2 있음 | 웹 UI 표시 |
|
||||
| 없음 + 인터넷 사용 가능 | 번들 설정이 허용하면 WebView2 웹 부트스트래퍼를 BA 선행 패키지로 설치한 뒤 웹 UI 표시. 실패하면 기본 네이티브 화면 |
|
||||
| 없음 + 오프라인 | 기본 네이티브 화면으로 같은 흐름 진행 |
|
||||
| 무인 설치 | WebView2를 사용하지 않음 |
|
||||
|
||||
기본 네이티브 화면은 같은 카탈로그의 필수 화면(`welcome`, `license`, `installFolder`, `review`, `progress`, 상태 화면)과 같은 검증을 제공하며 디자인 편집은 문구·로고로 제한한다. WebView2 사용자 데이터 폴더는 실행마다 임시 폴더에 만들고 종료 시 삭제한다.
|
||||
|
||||
## 보안
|
||||
|
||||
- 웹 UI는 번들 안 정적 파일만 사용한다. 원격 스크립트, 외부 CDN, `eval`을 쓰지 않고 CSP는 `default-src 'self'`로 둔다.
|
||||
- 릴리스 빌드는 개발자 도구, 상황 메뉴, 확대·축소 단축키를 끈다.
|
||||
- 웹 UI는 파일·레지스트리·프로세스에 직접 접근하지 않는다. 모든 요청은 정해진 메시지로 호스트를 거치고 호스트가 다시 검증한다.
|
||||
- BA와 웹 UI는 일반 사용자 권한으로 실행한다. 권한 상승은 Burn이 관리하며 UAC 화면은 꾸미지 않는다.
|
||||
- 로그와 메시지에 토큰·비밀번호를 넣지 않는다.
|
||||
|
||||
## 1단계 범위와 검증 실험
|
||||
|
||||
첫 구현은 신규 설치·업그레이드·제거와 실패·취소를 확인할 수 있는 화면 10개로 한다.
|
||||
|
||||
`welcome`, `license`, `installFolder`, `review`, `progress`, `finish`, `failure`, `cancelled`, `maintenance`, `uninstallConfirm`
|
||||
|
||||
`features`, `tasks`, `filesInUse`, `rebootRequired`, `blocked`는 MSI 기능·바로가기·파일 연결을 프로젝트 모델에 넣는 다음 단계에서 추가한다.
|
||||
|
||||
본 구현 전에 백로그 BL-08의 검증 실험을 VM에서 수행한다. MSI 1개와 선행 프로그램 1개를 담은 Burn 번들에 C# 호스트와 웹 UI를 붙이고 아래를 확인한다.
|
||||
|
||||
- 신규 설치, 같은 UpgradeCode의 상위 버전 업그레이드, 제거, 복구
|
||||
- 설치 폴더 변경과 잘못된 경로 거부
|
||||
- 진행률 표시, 적용 중 취소와 롤백 결과 표시
|
||||
- 실패 화면과 로그 위치
|
||||
- `/quiet` 무인 설치에서 WebView2 미사용
|
||||
- WebView2가 없는 PC에서 기본 네이티브 화면으로 설치 완료
|
||||
- 고해상도(DPI), 키보드 이동, 스크린 리더 읽기
|
||||
|
||||
실험 결과는 `src/tests/verification-YYYY-MM-DD-<slug>.json`에 실행·미실행 항목을 모두 기록한다. 실험 전에는 이 구조를 검증된 것으로 표시하지 않는다.
|
||||
|
||||
## 미결정 사항
|
||||
|
||||
- WiX 버전(v6 또는 v7)과 OSMF 이용 조건 수용 여부
|
||||
- 지원 대상 PC 범위(Windows 10 LTSC·Server·오프라인 PC 포함 여부)와 WebView2 웹 부트스트래퍼 사용 허용 여부
|
||||
- 약관 파일 형식과 무인 설치의 동의 전달 방식
|
||||
- 기본 지원 언어 목록
|
||||
- 기본 네이티브 화면을 WPF로 할지, 같은 호스트의 최소 Win32 화면으로 할지
|
||||
|
||||
## 근거
|
||||
|
||||
- [FireGiant — Out-of-process bootstrapper applications](https://docs.firegiant.com/wix/whatsnew/oopbas/): WiX v5부터 BA를 별도 실행 파일로 실행, C++·.NET BA API 제공
|
||||
- [FireGiant — WixStdBA](https://docs.firegiant.com/wix/tools/burn/wixstdba/): 표준 BA 테마의 지원 범위
|
||||
- [Microsoft Learn — Distribute your app and the WebView2 Runtime](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution): Evergreen·고정 버전 런타임 배포 방식
|
||||
- [Microsoft Learn — Overview of WebView2 features and APIs](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/overview-features-apis): 가상 호스트 매핑, 웹 메시지 통신
|
||||
- [Inno Setup — WizardStyle](https://jrsoftware.org/ishelp/topic_setup_wizardstyle.htm), [Revenera — Defining InstallShield Prerequisites](https://docs.revenera.com/installshield/helplibrary/IHelpSetPrereqCustom.htm): 기본 화면 구성 참고
|
||||
@@ -0,0 +1,82 @@
|
||||
# EXE 버전과 누적 패치
|
||||
|
||||
결정일: 2026-11-09. EXE 프로젝트의 버전을 **메이저 버전**과 **패치 버전**으로 나누고, 패치 버전마다 전체 설치 파일과 누적 패치 파일을 함께 만든다. 이 문서는 설계다. 버전 종류·ProductCode 상속·패치 제약 검사는 모델과 화면에 구현했지만 MSI·MSP 생성, Burn 번들, 실제 설치·패치 시험은 EXE 빌드와 함께 후속으로 구현한다.
|
||||
|
||||
버전 메타파일의 위치와 형식은 [프로젝트 모델](02-project-model.md), 설치 화면과 Burn 구조는 [14 EXE 설치 UI](14-exe-installer-ui.md)를 따른다.
|
||||
|
||||
## 결정
|
||||
|
||||
| 항목 | 결정 |
|
||||
| --- | --- |
|
||||
| 버전 종류 | 버전마다 `updateType`을 둔다. `major`는 새 ProductCode를 받고 이전 제품을 제거한 뒤 설치한다(메이저 업그레이드). `patch`는 아래쪽 메이저 버전의 ProductCode를 그대로 쓴다(마이너 업그레이드). 기존 버전 파일에 값이 없으면 `major`다. |
|
||||
| 라인 | 메이저 버전과, 그보다 높고 다음 메이저 버전보다 낮은 패치 버전들을 한 **라인**으로 본다. 예: 1.0.0(메이저, A)·1.0.1·1.0.2·1.0.3(패치, A) / 1.1.0(메이저, B)·1.1.1(패치, B). |
|
||||
| 패치 기준 | **누적 패치**. 1.0.3 패치 파일 하나가 같은 라인의 이전 버전 모두(1.0.0·1.0.1·1.0.2)를 1.0.3으로 올린다. 이전 패치를 대체(supersede)하므로 사용자는 최신 패치 하나만 설치한다. |
|
||||
| 산출물 | 메이저 버전: 전체 설치 파일 `setup.exe`. 패치 버전: 전체 설치 파일 `setup.exe`(새 PC에서 바로 그 버전 설치)와 패치 파일(누적 MSP와 이를 감싼 패치 EXE) 두 개. |
|
||||
| 설치 경로 | 같은 라인의 이전 버전이 설치된 PC는 **패치 파일로만** 업데이트한다. 전체 설치 파일은 그 경우 설치하지 않고 패치 파일을 안내한다. |
|
||||
| 기준 보존 | 패치가 올릴 이전 버전은 잠가야 한다. 잠긴 버전의 빌드 결과(MSI, `.wixpdb`, 실제 파일)는 패치 생성의 기준으로 보존하며 지우지 않는다. |
|
||||
|
||||
## 설치 동작
|
||||
|
||||
`setup.exe`와 패치 EXE는 Burn이 설치된 제품(UpgradeCode·ProductCode·버전)을 탐지한 뒤 다음처럼 동작한다. 1.0.3이 패치 버전인 경우다.
|
||||
|
||||
| PC 상태 | 1.0.3 전체 설치 파일 | 1.0.3 패치 파일 |
|
||||
| --- | --- | --- |
|
||||
| 설치 없음 | 1.0.3을 새로 설치 | 적용 대상 없음, 전체 설치 파일 안내 |
|
||||
| 1.0.0·1.0.1·1.0.2 (같은 라인) | 설치하지 않고 패치 파일 안내 | 1.0.3으로 업데이트 |
|
||||
| 1.0.3 | 유지·복구 화면 | 이미 적용됨 |
|
||||
| 0.9.x (이전 라인, 다른 ProductCode) | 메이저 업그레이드: 0.9.x 제거 후 1.0.3 설치 | 적용 대상 없음, 전체 설치 파일 안내 |
|
||||
| 1.1.0 이상 (더 높은 버전) | 낮은 버전 설치 거부 | 적용 대상 없음 |
|
||||
|
||||
1.0.3 전체 설치 파일로 새로 설치한 PC도 같은 라인이다. 이후의 1.0.4 누적 패치는 1.0.3도 기준에 포함하므로 그대로 적용된다. 따라서 패치 버전의 전체 설치 파일도 출시하면 잠그고 기준으로 보존한다.
|
||||
|
||||
## 누적 패치 생성
|
||||
|
||||
- 패치 MSP는 같은 라인의 이전 버전마다 하나씩 기준(WiX `PatchBaseline`)을 두고, 각 기준의 빌드 결과에서 이번 버전으로 가는 변환을 한 파일에 담는다.
|
||||
- 같은 라인의 패치는 하나의 패치 계열(`PatchFamily`)로 묶고 순서 값을 올려, 새 패치가 이전 패치를 대체한다.
|
||||
- 기준의 빌드 결과는 잠근 시점의 버전 파일 해시, 공유 설정 해시, 파일 목록·해시와 함께 `releases/`에 둔다. 패치 빌드는 이 해시를 다시 확인하고, 다르면 `BaselineChanged`로 거부한다. 공유 설정(아키텍처·설치 폴더·설치 범위·UpgradeCode·속성)을 출시 후 바꾸면 그 라인의 패치를 만들 수 없다.
|
||||
- MSI 구성 요소 GUID는 설치 경로에서 결정되도록 만들어 같은 경로의 파일이 버전 사이에 같은 구성 요소로 유지되게 한다.
|
||||
|
||||
## 패치 제약
|
||||
|
||||
Windows Installer 패치는 이전 버전이 설치한 구성 요소를 지우거나 옮길 수 없다. 입력 검사(`validate_version_line`)는 버전 파일 수준에서 다음을 확인하며, 규칙은 그대로인데 소스 폴더에서 파일이 사라진 경우는 패치 빌드에서 기준 파일 목록과 비교해 거부한다.
|
||||
|
||||
| 코드 | 조건 |
|
||||
| --- | --- |
|
||||
| `MissingMajorVersion` | 패치 버전보다 낮은 메이저 버전이 없다. |
|
||||
| `PatchProductCodeMismatch` | 패치의 ProductCode가 라인 메이저 버전과 다르다. |
|
||||
| `BaselineNotLocked` | 패치가 올릴 이전 버전 중 잠기지 않은 버전이 있다. |
|
||||
| `PatchRemovesFiles` | 이전 버전의 파일 규칙(ID 기준)을 지웠다. |
|
||||
| `PatchMovesFiles` | 같은 파일 규칙의 소스 또는 설치 위치를 바꿨다. |
|
||||
| `PatchRemovesRegistry` | 이전 버전의 레지스트리 값을 지웠거나 루트·뷰·키 경로·값 이름을 바꿨다. 형식·데이터 변경은 허용한다. |
|
||||
| `PatchRemovesShortcut` | 이전 버전의 바로가기를 지웠거나 위치·폴더·이름을 바꿨다. 대상·인수 변경은 허용한다. |
|
||||
| `ProductCodeReused` | 메이저 버전이 다른 메이저 버전의 ProductCode를 쓴다. |
|
||||
|
||||
파일·레지스트리·바로가기 추가, 포함·제외 패턴과 데이터 변경은 허용한다. 지우거나 옮겨야 하면 새 메이저 버전을 만든다.
|
||||
|
||||
## 제작기 화면
|
||||
|
||||
- 설치 구성 › 버전 목록에 **종류** 열(메이저 / 패치 · 라인 메이저 버전)을 둔다.
|
||||
- 버전 목록 오른쪽 위 **버전 추가** 팝업에서 새 버전 번호, **종류**(번호를 입력할 때마다 자동 선택: 아래쪽 메이저 버전과 Major·Minor가 같으면 패치, 아니면 메이저. 직접 바꿀 수 있지만 아래에 메이저 버전이 없는 최초 버전은 패치 선택지가 비활성화된다), 복사할 버전을 고르고 **만들기**로 추가한다. 패치는 라인 메이저 버전의 ProductCode를 받고, 낮은 메이저 버전이 없으면 `MissingMajorVersion`으로 추가하지 않는다. 첫 버전은 항상 메이저다.
|
||||
- 버전 › 일반 탭은 종류를 표시하고, 패치 버전의 ProductCode는 읽기 전용으로 보여 준다(새 GUID 버튼 없음).
|
||||
- 입력 검사는 선택한 버전에 대해 위 패치 제약을 함께 검사하고 해당 탭으로 이동한다.
|
||||
|
||||
## 구현 범위
|
||||
|
||||
| 항목 | 상태 |
|
||||
| --- | --- |
|
||||
| `ExeVersion.updateType`, `line_major`, `patch_baselines`, `validate_version_line` | 구현 |
|
||||
| 버전 목록 종류 열, 종류 선택 추가, ProductCode 상속, 일반 탭 표시 | 구현 |
|
||||
| 기준 빌드 결과 보존·해시 기록, `BaselineChanged` | 후속 (EXE 빌드) |
|
||||
| MSI·누적 MSP·Burn 전체 설치/패치 EXE 생성, 설치 상태별 안내 | 후속 (EXE 빌드) |
|
||||
| 같은 라인 PC에서 전체 설치 파일 거부, 패치 대체 순서 | 후속 (BA 구현) |
|
||||
| Windows VM 설치·패치·제거 시험 | 미실행 |
|
||||
|
||||
## 근거
|
||||
|
||||
- [Windows Installer: Patching and Upgrades](https://learn.microsoft.com/en-us/windows/win32/msi/patching-and-upgrades)
|
||||
- [Windows Installer: Changing the Product Code](https://learn.microsoft.com/en-us/windows/win32/msi/changing-the-product-code)
|
||||
- [Windows Installer: Minor Upgrades](https://learn.microsoft.com/en-us/windows/win32/msi/minor-upgrades)
|
||||
- [Windows Installer: Sequencing Patches](https://learn.microsoft.com/en-us/windows/win32/msi/sequencing-patches)
|
||||
- [Windows Installer: MsiPatchSequence Table](https://learn.microsoft.com/en-us/windows/win32/msi/msipatchsequence-table)
|
||||
- [WiX: Building patches](https://docs.firegiant.com/wix/tools/patches/)
|
||||
- [WiX: PatchBaseline](https://docs.firegiant.com/wix/schema/wxs/patchbaseline/)
|
||||
@@ -0,0 +1,128 @@
|
||||
# 16 스마트 앱 컨트롤 검사 대상과 서명 범위
|
||||
|
||||
2026-11-09 작성. 스마트 앱 컨트롤(Smart App Control, 이하 SAC)이 켜진 PC에서 설치한 제품이 동작하려면 무엇을 서명하고 무엇을 배포하지 말아야 하는지 정리한 참조 문서다. 서명 시스템(EXE 출력의 서명 단계, [05 릴리스 생성](05-release-pipeline.md))을 설계할 때 입력으로 쓴다. 이 문서는 조사 결과이며 구현이나 SAC 실제 시험 통과를 뜻하지 않는다. 정책 결정은 별도 변경 기록으로 남긴다.
|
||||
|
||||
## SAC 판정 순서
|
||||
|
||||
1. Microsoft 클라우드 평판 서비스가 안전 여부를 확신할 수 있으면 그 판정대로 허용하거나 차단한다. 유효한 서명이 있어도 악성 판정이면 차단된다.
|
||||
2. 확신할 수 없으면 서명을 본다. 유효한 서명이 있으면 허용하고, 서명이 없거나 유효하지 않으면 차단한다.
|
||||
3. 앱별·파일별 예외를 둘 수 없다. 사용자 선택지는 SAC를 끄는 것뿐이다. 최근 업데이트부터 끈 뒤 다시 켤 수 있다.
|
||||
|
||||
SmartScreen은 진입 실행 파일 하나만 평판을 보지만, SAC는 운영체제 로더와 스크립트 엔진이 불러오는 **모든 코드**를 검사한다. 서명된 설치기가 서명 없는 코드를 풀어 놓는 방식은 통과하지 못한다.
|
||||
|
||||
## 서명 요건
|
||||
|
||||
| 요건 | 내용 |
|
||||
| --- | --- |
|
||||
| 신뢰 루트 | Microsoft 신뢰 루트 프로그램(Trusted Root Program)에 속한 루트로 이어져야 한다. 사용자나 관리자가 직접 등록한 루트(개발용 인증서 포함)는 인정되지 않는다. |
|
||||
| 다이제스트 | SHA-256 이상을 쓴다(`signtool sign /fd sha256`). |
|
||||
| 키 알고리즘 | RSA를 쓴다. ECC는 지원되지만 호환성을 위해 피한다. |
|
||||
| 타임스탬프 | SAC 자체 요건으로 명시된 자료는 없다. 인증서 만료 뒤에도 서명이 유효하도록 반드시 붙인다(`/tr <TSA URL> /td sha256`). |
|
||||
| 서명 후 불변 | 서명 뒤 리소스·버전 정보를 수정하면 서명이 깨진다. 모든 후처리는 서명 전에 끝낸다. |
|
||||
| 스크립트 서명 | `WinVerifyTrust`로 검증하며, 서명 루트가 대상 PC의 신뢰 루트 저장소에 있어야 한다. 공인 루트는 이 조건을 충족한다. |
|
||||
|
||||
## 검사 대상 분류
|
||||
|
||||
### A. 실행 코드(PE) — 반드시 서명
|
||||
|
||||
확장자가 아니라 파일 헤더(`MZ`와 PE 서명)로 판별한다. 운영체제 로더는 확장자와 관계없이 이미지를 불러올 때 검사한다.
|
||||
|
||||
- Microsoft가 명시한 확장자: `.exe`, `.com`, `.dll`, `.rll`, `.ocx`, `.sys`
|
||||
- MotW 차단 목록에도 있는 PE 확장자: `.cpl`, `.drv`, `.scr`, `.vxd`
|
||||
- 제품 고유 확장자 예(EG-BIM): `.irx`, `.tx`, `.txv`, `.txr`
|
||||
- .NET 어셈블리(관리 코드 DLL)도 PE이므로 같은 기준으로 서명한다.
|
||||
|
||||
### B. Windows Installer — 서명
|
||||
|
||||
- `.msi`, `.msp`: 서명한다. MSI가 설치 중 임시 폴더에 푸는 사용자 지정 작업(Custom Action) DLL·EXE도 A와 같은 기준으로 미리 서명한다.
|
||||
- `.mst`: App Control의 검사 대상이다. 서명 방법과 필요 여부는 확인이 필요하다.
|
||||
|
||||
### C. 패키지 — 패키지 서명
|
||||
|
||||
- `.appx`, `.appxbundle`, `.msix`, `.msixbundle`: 패키지 서명으로 처리한다. 패키지 안의 PE를 개별 서명해야 하는지는 SAC 시험으로 확인한다.
|
||||
|
||||
### D. 스크립트(App Control 연동 스크립트 호스트) — 서명하거나 배포하지 않음
|
||||
|
||||
| 호스트 | 확장자 | 서명이 없을 때 |
|
||||
| --- | --- | --- |
|
||||
| PowerShell | `.ps1`, `.psm1`, `.psd1` | 차단되지 않고 제한 언어 모드(Constrained Language Mode)로 실행된다. 기능이 조용히 실패할 수 있다. 진입 스크립트가 불러오는 모듈과 dot-source 파일도 모두 서명해야 한다. |
|
||||
| Windows Script Host(`wscript.exe`, `cscript.exe`) | `.vbs`, `.js`, `.wsf` | 차단된다. |
|
||||
| Windows Script Host(인코딩 스크립트) | `.vbe`, `.jse` | 배포하지 않는다. |
|
||||
|
||||
### E. 사용 금지
|
||||
|
||||
- `.hta`(`mshta.exe`): 스크립트 검사가 켜진 App Control 정책이 하나라도 있으면 모든 실행이 차단된다. SAC 환경에서도 같다고 보고 배포하지 않는다.
|
||||
- MSXML을 통한 스크립트 실행(XSL 스크립트 등): 위와 같은 이유로 쓰지 않는다.
|
||||
|
||||
### F. 직접 통제 밖 — 서명 대상 아님, 단 실행되는 하위 프로그램은 검사됨
|
||||
|
||||
- `.bat`, `.cmd`(`cmd.exe`): App Control이 직접 통제하지 않으며 서명할 수도 없다. 배치 파일이 실행하는 EXE와 DLL은 검사된다.
|
||||
- 앱이 스스로 해석하는 데이터·스크립트(LISP, CUI, DCL, 도면, 이미지 등): 검사 대상이 아니다.
|
||||
- App Control과 연동하지 않는 타사 스크립트 엔진(Python, Java 등): 엔진 실행 파일은 A 기준으로 검사되지만, 그 엔진이 실행하는 스크립트는 통제되지 않는다.
|
||||
|
||||
### G. 인터넷에서 받은 파일(MotW)에 대한 차단 목록
|
||||
|
||||
웹 표식(Mark of the Web, MotW)이 붙은 아래 확장자는 열기 자체가 차단되고 무시 선택지가 없다. 목록은 고정이며 레지스트리로 바꿀 수 없다. 2026-04 기준 자료이므로 서명 시스템에는 갱신 가능한 데이터로 둔다.
|
||||
|
||||
`.appref-ms`, `.appx`, `.appxbundle`, `.bat`, `.chm`, `.cmd`, `.com`, `.cpl`, `.dll`, `.drv`, `.gadget`, `.hta`, `.iso`, `.js`, `.jse`, `.lnk`, `.msc`, `.msp`, `.ocx`, `.pif`, `.ppkg`, `.printerexport`, `.ps1`, `.rdp`, `.reg`, `.scf`, `.scr`, `.settingcontent-ms`, `.sys`, `.url`, `.vb`, `.vbe`, `.vbs`, `.vhd`, `.vhdx`, `.vxd`, `.wcx`, `.website`, `.wsf`, `.wsh`
|
||||
|
||||
배포와 업데이트에 적용하는 규칙:
|
||||
|
||||
- 배포물은 서명된 `.exe` 또는 `.msi`로 직접 내려받게 한다. `.iso`, `.vhd`, `.vhdx`로 감싸지 않는다.
|
||||
- 업데이트가 받는 파일에 이 목록의 확장자를 쓰지 않는다. 패치는 서명된 설치 파일 형태로 받아 실행한다.
|
||||
- MotW를 지워서 차단을 피하는 방식은 쓰지 않는다.
|
||||
- 설치기가 디스크에 쓴 파일에는 보통 MotW가 붙지 않는다. 따라서 설치된 `.chm`, `.bat` 등은 이 목록의 영향을 받지 않는 것으로 본다. 이 판단은 SAC 시험으로 확인한다.
|
||||
|
||||
## 서명 시스템 반영 요구사항
|
||||
|
||||
1. **PE 판별**: 수집한 모든 파일을 헤더로 검사해 PE를 찾는다. 확장자 목록만으로 판정하지 않는다([05 릴리스 생성](05-release-pipeline.md)의 기존 원칙).
|
||||
2. **확장자 분류 데이터**: B~G 분류를 코드에 고정하지 않고 버전이 붙은 데이터로 둔다. 분류 결과를 서명 보고서에 기록한다.
|
||||
3. **파일별 정책**: 기존 `sign-owned`, `verify-vendor`, `not-applicable`을 유지한다. 서명 없는 타사 PE(오픈소스 등)를 처리할 정책은 결정이 필요하다.
|
||||
4. **거부 규칙**: 정식 빌드에서 아래를 발견하면 설명과 함께 거부한다.
|
||||
- 서명할 수 없거나 서명되지 않은 PE
|
||||
- D 분류의 서명 없는 스크립트
|
||||
- E 분류 파일
|
||||
- 배포물 형식이 G 목록에 해당하는 경우
|
||||
|
||||
오류 코드는 구현 시 정하고 `src/README.md`에 적는다.
|
||||
5. **서명 검증**: 서명 후 모든 대상 파일을 다시 검증하고 아래 항목을 기록한다. 허용할 루트 인증서의 지문은 계획에 고정한다.
|
||||
- 다이제스트가 SHA-256 이상인지
|
||||
- 체인과 루트 지문
|
||||
- 타임스탬프 유무
|
||||
- 서명자
|
||||
6. **실행 중 생성 코드 점검표**: 정적 검사로 찾을 수 없으므로, 제품마다 아래 질문을 확인 항목으로 둔다.
|
||||
- 업데이트가 내려받는 파일은 무엇인가
|
||||
- 자체 압축 해제 EXE를 만드는 기능이 있는가
|
||||
- 임시 폴더에 DLL을 풀어서 쓰는가
|
||||
- 실행 중에 코드를 컴파일해 디스크에 저장하는가
|
||||
|
||||
## EG-BIM 조사 결과
|
||||
|
||||
2026-11-09 이 PC에 설치된 EG-BIM(`C:\Program Files\Baron\EG-BIM`)을 읽기 전용으로 조사했다. SAC를 켠 실제 실행 시험은 하지 않았다.
|
||||
|
||||
| 항목 | 결과 |
|
||||
| --- | --- |
|
||||
| PE 파일 | 965개. 서명 유효 620개, 서명 없음 344개, 판정 오류 1개(`IntelliConvert.com`) |
|
||||
| 바론 서명 파일 | 6개(`Grimi.exe`, `Kkureomi.exe`, `LoadFile.exe`, `EGBIM.Drawer.Splash.exe`, `Nyng.PlotWindow.exe`, `PlotStyleTableEditor.exe`) |
|
||||
| 바론 인증서 | GlobalSign GCC R45 CodeSigning CA 2020 발급, 루트 GlobalSign Code Signing Root R45(`AuthRoot` 저장소에 있음), 2026-08-10~2027-08-11, 온라인 폐기 확인 통과, 타임스탬프 있음 |
|
||||
| 서명 없는 PE 확장자 | `.dll` 294, `.irx` 31, `.exe` 9, `.tx`·`.txv`·`.txr` 10, `.com` 1 |
|
||||
| 그 밖의 SAC 관련 파일 | `.chm` 9개, `.bat` 1개(`Nyng.bat`). `.ps1`, `.vbs`, `.js`, `.hta`, `.msi`는 없음 |
|
||||
| 실행 중 생성 코드 후보 | `Extractor.exe`(자체 압축 해제 스텁으로 보임), `BaronSoftware.Update.dll`(업데이트) |
|
||||
|
||||
## SAC 시험 항목(미실행)
|
||||
|
||||
SAC가 켜진 Windows 11 VM 스냅샷에서 실행하고 `CodeIntegrity/Operational`(3076, 3077, 3089)과 `AppLocker/MSI and Script`(8028, 8029, 8037, 8038) 이벤트를 수집한다.
|
||||
|
||||
- 모든 PE를 서명한 빌드의 설치·실행·주요 기능·업데이트·제거
|
||||
- `.irx`, `.tx` 등 비표준 확장자 PE가 검사·차단되는지
|
||||
- 서명된 `.dll`, `.msp`에 MotW가 붙었을 때 G 목록 차단이 서명으로 풀리는지
|
||||
- MSIX 내부 PE를 개별 서명하지 않아도 되는지
|
||||
- 설치된 `.chm`, `.bat`이 영향받지 않는지
|
||||
- 첫 실행과 두 번째 실행의 시작 시간 차이
|
||||
|
||||
## 근거
|
||||
|
||||
- [What is Smart App Control? — Microsoft Support](https://support.microsoft.com/en-us/topic/what-is-smart-app-control-285ea03d-fa88-4d56-882e-6698afdb7003): 판정 순서, 예외 불가, 재활성화
|
||||
- [App Control for Business feature availability — Microsoft Learn](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/feature-availability): 검사 대상 파일 형식
|
||||
- [Understand App Control script enforcement — Microsoft Learn](https://learn.microsoft.com/en-us/windows/security/application-security/application-control/app-control-for-business/design/script-enforcement): 스크립트 호스트별 동작, `cmd.exe` 비통제, 이벤트 ID
|
||||
- [Smart App Control — text/plain(Eric Lawrence, Microsoft Defender 소속 개인 블로그, 2026-04-28)](https://textslashplain.com/2026/04/28/smart-app-control/): MotW 차단 목록, 서명 요건. 공식 문서가 아니므로 시험으로 확인한다.
|
||||
@@ -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,42 @@
|
||||
| [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개 모드, 페이지·취소·실패·재부팅 및 편집 화면 계약 |
|
||||
| [14 EXE 설치 UI](14-exe-installer-ui.md) | WiX MSI + Burn, C# BA 호스트와 WebView2 웹 UI, 기본 화면 카탈로그, `installer-ui.json`, WebView2 대체 경로 |
|
||||
| [15 EXE 버전과 누적 패치](15-exe-versions-and-patches.md) | 메이저/패치 버전, 라인과 ProductCode, 누적 MSP와 전체 설치 파일, 설치 상태별 동작, 패치 제약 검사 |
|
||||
| [16 스마트 앱 컨트롤 검사 대상과 서명 범위](16-smart-app-control-signing-scope.md) | SAC 판정 순서, 서명 요건, 확장자 분류, MotW 차단 목록, 서명 시스템 요구사항, EG-BIM 조사 결과 |
|
||||
| [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,20 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"productCode": null,
|
||||
"updateType": "major",
|
||||
"locked": false,
|
||||
"files": [
|
||||
{
|
||||
"id": "application",
|
||||
"source": "appPayload",
|
||||
"include": [
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [],
|
||||
"targetRelativePath": "",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"registry": [],
|
||||
"shortcuts": []
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"product": {
|
||||
"displayName": "Sample Desktop Tool",
|
||||
"manufacturer": "Example Publisher",
|
||||
"displayVersion": "1.0.0"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"alias": "appPayload"
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"formats": {
|
||||
"exe": {
|
||||
"architecture": "x64",
|
||||
"upgradeCode": null,
|
||||
"installScope": "perMachine",
|
||||
"installRoot": "[ProgramFiles64Folder]Example Publisher\\Sample Desktop Tool",
|
||||
"properties": []
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"id": "exe-direct",
|
||||
"format": "exe",
|
||||
"delivery": "offline-singlefile",
|
||||
"distribution": "direct",
|
||||
"signingProfileRef": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 }
|
||||
]
|
||||
}
|
||||
@@ -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,266 @@
|
||||
# 처음 시작하는 Azure 아티팩트 서명(Artifact Signing) 운영 서명
|
||||
|
||||
대상: Installer Studio로 만든 MSIX를 **사내 시험을 넘어 실제 사용자 PC에 배포**하려는 담당자. 자체 서명 테스트 인증서 대신 공인 신뢰 서명을 처음 준비하는 사람 기준으로 쓴다.
|
||||
|
||||
확인일: **2026-11-03, 한국 시간**. Microsoft Learn 한국어·영어 문서(2026-10-08 갱신판)를 확인해 작성했으며 실제 계정 생성·신원 확인·서명을 수행한 기록은 아니다. 메뉴 이름은 **Azure 포털 한국어 화면 기준**으로 적고 영문을 괄호로 병기한다. Learn 한국어 페이지는 기계 번역이므로 실제 포털 표기가 약간 다를 수 있다. 2026년에 서비스 이름이 **Trusted Signing → Artifact Signing(아티팩트 서명)**으로 바뀌었다. 포털 검색, 역할 이름, winget 패키지 ID에는 옛 이름이 남아 있으므로 두 이름을 같은 것으로 읽는다.
|
||||
|
||||
## 이 방식이 하는 일
|
||||
|
||||
Azure가 인증서 발급·보관·갱신을 모두 맡고, 우리 PC에서는 SignTool이 Azure에 서명 요청만 보낸다. 인증서 개인 키는 Azure HSM 안에 있어 USB 토큰이나 PFX 파일이 없다. 인증서는 3일마다 자동 교체되지만 **주체(Subject: CN, O, L, S, C)는 신원 확인 결과로 고정**되므로 MSIX의 Publisher가 바뀌지 않는다.
|
||||
|
||||
```text
|
||||
Azure 구독 준비 → 리소스 공급자 등록 → 아티팩트 서명 계정 만들기
|
||||
→ 조직 ID 유효성 검사(1~20영업일) → 인증서 프로필 만들기 → 역할 할당
|
||||
→ 서명 PC에 클라이언트 도구 설치 → metadata.json 작성 → az login → signtool로 MSIX 서명
|
||||
→ 서명 검증 → Installer Studio Publisher를 인증서 주체로 맞춤
|
||||
```
|
||||
|
||||
## 용어 대응표
|
||||
|
||||
| 포털 한국어 표기 | 영어 | 뜻 |
|
||||
| --- | --- | --- |
|
||||
| 아티팩트 서명 계정 | Artifact Signing account | 서명 서비스의 최상위 리소스 |
|
||||
| ID 유효성 검사 | Identity validation | 회사(또는 개인) 신원 확인 요청 |
|
||||
| 인증서 프로필 | Certificate profile | 실제 인증서가 발급되는 단위 |
|
||||
| 공용 / 프라이빗 | Public / Private | 공개 배포용 / 사내 전용 |
|
||||
| 액세스 제어(IAM) | Access control (IAM) | 역할 부여 메뉴 |
|
||||
| 아티팩트 서명 ID 검증 도구 | Artifact Signing Identity Verifier | 신원 확인 요청 권한 |
|
||||
| 아티팩트 서명 인증서 프로필 서명자 | Artifact Signing Certificate Profile Signer | 서명 실행 권한 |
|
||||
| 인증서 제목(주체) 미리 보기 | Certificate subject preview | 인증서에 들어갈 CN·O 문자열 |
|
||||
|
||||
## 시작 전 확인
|
||||
|
||||
| 항목 | 내용 |
|
||||
| --- | --- |
|
||||
| 지원 국가 | 공용 신뢰(Public Trust) 인증서는 미국, 캐나다, EU, 영국, 호주, 뉴질랜드, 일본, **한국**, 싱가포르, 스위스, 노르웨이, 이스라엘의 **조직**에 제공한다. 개인 개발자는 미국·캐나다만 가능하다. 한국 법인은 조직으로 진행한다. |
|
||||
| Azure 지역 | 계정은 지원 지역에만 만들 수 있다. **Korea Central**이 포함되어 있고 엔드포인트는 `https://krc.codesigning.azure.net`이다. |
|
||||
| 비용 | 기본(Basic)은 월 5,000회, 프리미엄(Premium)은 월 100,000회 서명을 포함한다. 금액은 가격 페이지의 계산기에서 확인한다(제3자 자료 기준 월 약 10달러·100달러). **계정을 만든 날부터 과금되고 일할 계산이 없다.** 신원 확인이 끝나기 전에도 요금이 나간다. |
|
||||
| 대상 Windows | Windows 10 1809 이상, Windows 11, Windows Server 2016 이상이 루트를 신뢰한다. 더 오래된 PC는 지원 범위에서 제외한다. |
|
||||
| SmartScreen | 서명해도 즉시 신뢰되지 않는다. 설치 실적이 쌓이기 전에는 경고가 뜰 수 있으며 정상 동작이다. |
|
||||
| 필요한 사람 | Azure 구독 소유자(역할 부여), 회사 대표로 개인 신원 확인을 할 담당자(신분증과 스마트폰), 서명을 실행할 개발자. |
|
||||
|
||||
## 1단계. Azure 구독과 테넌트 준비
|
||||
|
||||
1. [Azure 포털](https://portal.azure.com/)에 회사 계정으로 로그인한다. 회사 Microsoft Entra 테넌트가 없으면 먼저 만든다.
|
||||
2. **구독(Subscriptions)**이 있어야 한다. 없으면 구독을 만들고 결제 수단을 등록한다. 조직 신원 확인에는 청구 계정 유형 조건이 없다(개인 확인에만 "개인(Individual)" 유형이 필요하다).
|
||||
3. 검색 상자에 **구독**을 입력해 열고 대상 구독을 선택한다. 왼쪽 메뉴 **설정 → 리소스 공급자(Resource providers)**에서 `Microsoft.CodeSigning`을 찾아 줄임표(…) → **등록(Register)**을 누른다. 상태가 **등록됨(Registered)**으로 바뀌면 된다.
|
||||
|
||||
Azure CLI를 쓰면 같은 작업은 다음과 같다.
|
||||
|
||||
```powershell
|
||||
az login
|
||||
az account set -s <구독 ID>
|
||||
az provider register --namespace "Microsoft.CodeSigning"
|
||||
az extension add --name artifact-signing
|
||||
```
|
||||
|
||||
## 2단계. 아티팩트 서명 계정 만들기
|
||||
|
||||
1. 포털 검색 상자에 **아티팩트 서명 계정(Artifact Signing Accounts)**을 입력해 열고 **만들기(Create)**를 누른다.
|
||||
2. 입력값
|
||||
- **구독(Subscription)**: 1단계의 구독
|
||||
- **리소스 그룹(Resource group)**: **새로 만들기(Create new)**. 예: `rg-codesigning`
|
||||
- **계정 이름(Account name)**: 3~24자 영숫자, 전 세계에서 유일, 문자로 시작. 예: `baronsigning`
|
||||
- **지역(Region)**: **Korea Central**
|
||||
- **가격 책정(Pricing)**: 처음에는 **기본(Basic)**
|
||||
3. **검토 + 만들기(Review + create) → 만들기(Create)**. 완료되면 **리소스로 이동(Go to resource)**.
|
||||
4. 계정 **개요(Overview)** 화면의 **계정 URI(Account URI)**를 적어 둔다. 뒤의 metadata.json에 들어간다.
|
||||
|
||||
```powershell
|
||||
az group create --name rg-codesigning --location koreacentral
|
||||
az artifact-signing create -n baronsigning -l koreacentral -g rg-codesigning --sku Basic
|
||||
```
|
||||
|
||||
## 3단계. 역할 할당
|
||||
|
||||
계정 화면 왼쪽 메뉴 **액세스 제어(IAM)** → **추가(Add) → 역할 할당 추가(Add role assignment)**. **역할(Role)** 탭의 검색란에 "아티팩트 서명"을 입력하면 두 개의 사용자 지정 역할이 보인다. 역할을 고른 뒤 **구성원(Members)** 탭에서 사람 또는 앱을 선택하고 **검토 + 할당(Review + assign)**을 누른다.
|
||||
|
||||
| 역할 | 누구에게 | 용도 |
|
||||
| --- | --- | --- |
|
||||
| **아티팩트 서명 ID 검증 도구** (Artifact Signing Identity Verifier, 구 Trusted Signing Identity Verifier) | 신원 확인을 신청할 담당자 | 4단계 **새 ID** 버튼 활성화. 구독에 **읽기 권한자(Reader)** 이상이 함께 있어야 한다 |
|
||||
| **아티팩트 서명 인증서 프로필 서명자** (Artifact Signing Certificate Profile Signer, 구 Trusted Signing Certificate Profile Signer) | 서명을 실행할 개발자 계정, 또는 CI용 앱 등록(서비스 주체) | 7단계 서명 호출 허용 |
|
||||
|
||||
계정과 인증서 프로필을 만드는 사람에게는 **기여자(Contributor)** 이상이 필요하다. 구독 **소유자(Owner)**라도 위 두 사용자 지정 역할이 없으면 해당 작업이 막힌다. 역할 반영에 몇 분 걸릴 수 있다.
|
||||
|
||||
### 여러 사람이 함께 서명할 때의 구조
|
||||
|
||||
서명자 역할을 사람마다 직접 주지 않고 **보안 그룹에 한 번만 주고 사람은 그룹에 넣고 뺀다.** 서명 PC에는 PFX나 비밀 값이 없으므로, 퇴사·이동 시 그룹에서 빼는 것만으로 서명이 즉시 막히고 인증서를 폐기할 필요가 없다.
|
||||
|
||||
1. **Microsoft Entra ID → 그룹(Groups) → 새 그룹(New group)**. 그룹 유형 **보안(Security)**, 이름 예 `sg-codesigning-signers`. 멤버십 유형은 **할당됨(Assigned)**.
|
||||
2. 3단계의 **역할 할당 추가**에서 **구성원** 탭의 **액세스 할당 대상**을 "사용자, 그룹 또는 서비스 주체"로 두고 그룹을 선택한다. 역할은 **아티팩트 서명 인증서 프로필 서명자**.
|
||||
3. 권한은 세 층으로 나눈다.
|
||||
|
||||
| 층 | 대상 | 역할 | 비고 |
|
||||
| --- | --- | --- | --- |
|
||||
| 소유 | 회사 대표 계정 1~2명 | 구독 **소유자** | 결제·역할 변경. 평소 서명에 쓰지 않는다 |
|
||||
| 운영 | 서명 인프라 담당 1~2명 | 리소스 그룹 **기여자** + **ID 검증 도구** | 계정·프로필 생성, 신원 확인 갱신 |
|
||||
| 서명 | `sg-codesigning-signers` 그룹 | **인증서 프로필 서명자** | 개발자 전원, CI용 앱 등록. 프로필을 바꾸거나 지울 수 없다 |
|
||||
|
||||
4. 사람마다 **자기 계정**으로 `az login`한다. 한 계정을 여럿이 공유하면 MFA·감사 기록·퇴사 처리가 모두 깨진다. 모든 서명자는 **같은 Entra 테넌트**에 있어야 하므로 대표 계정이 개인 Microsoft 계정의 기본 디렉터리라면 다른 사람을 **게스트 초대**해야 하고, 회사 도메인 테넌트가 있다면 그쪽에서 진행하는 것이 맞다.
|
||||
5. CI 서버는 **앱 등록(App registrations)**으로 서비스 주체를 만들어 같은 그룹에 넣는다. GitHub Actions·Azure DevOps는 클라이언트 비밀 대신 **페더레이션 자격 증명(워크로드 ID)**을 쓰면 비밀 저장이 없다.
|
||||
6. `metadata.json`은 비밀이 아니므로 저장소에 두고 공유한다. 팀·PC 구분이 필요하면 `"CorrelationId": "ci-main"`처럼 값을 넣어 서명 기록에서 구분한다.
|
||||
7. 서명 횟수는 계정 단위로 합산된다. 기본(Basic) 월 5,000회를 모든 서명자와 CI가 함께 쓴다. CI가 커밋마다 서명하면 빠르게 소진되므로 릴리스 빌드에서만 서명하거나 **프리미엄**으로 올린다. 기본 계층은 **공용 신뢰 프로필을 1개만** 만들 수 있다. 같은 회사의 여러 제품은 주체가 같으므로 프로필 1개를 함께 쓰면 된다.
|
||||
8. **서명 기록(Signing history)**은 서명자 역할이 볼 수 있고, 역할 변경은 **활동 로그(Activity log)**에 남는다. 역할 할당 변경에 경고 규칙을 걸어 두면 무단 추가를 감지할 수 있다.
|
||||
|
||||
## 4단계. 조직 ID 유효성 검사
|
||||
|
||||
가장 오래 걸리는 단계다. 처리 기간은 **영업일 기준 1~20일**이며 보완 서류 요청이 오면 더 길어진다. 여기서 입력한 회사명·주소가 그대로 인증서 주체가 되고 **MSIX Publisher**가 된다. 나중에 고치려면 ID 유효성 검사를 새로 해야 하고 앱 계열이 끊어지므로 처음부터 등기 서류와 똑같이 적는다.
|
||||
|
||||
1. 계정 화면 왼쪽 메뉴 **개체(Objects) → ID 유효성 검사(Identity validations)**를 연다.
|
||||
2. 상단에서 **조직(Organization)**을 선택하고 **새 ID(New identity) → 공용(Public)**을 누른다.
|
||||
3. **새 조직 유효성 검사(New organization validation)** 폼 입력값
|
||||
|
||||
| 항목(한국어 / 영어) | 적는 내용 |
|
||||
| --- | --- |
|
||||
| 조직 이름 / Organization Name | 법인 등기상의 정식 명칭. 영문 표기는 사업자등록증·영문 등기부의 표기와 일치 |
|
||||
| 웹 사이트 URL / Website url | 회사가 소유한 도메인의 웹사이트 |
|
||||
| 기본 이메일 / Primary Email | 회사 도메인의 실제 수신 가능한 메일. 확인 링크가 오며 7일 안에 열어야 한다 |
|
||||
| 보조 이메일 / Secondary Email | 기본과 다른 같은 도메인 메일. 메일 그룹 가능 |
|
||||
| 비즈니스 식별자 / Business Identifier | 사업자등록번호 또는 법인등록번호 |
|
||||
| 판매자 ID / Seller ID | Microsoft Store 판매자만. 없으면 비움 |
|
||||
| 도로명, 도시, 국가/지역, 주/도, 우편번호 | 법인 사업장 주소 |
|
||||
| 이름 / First Name, 성 / Last Name | 회사를 대표해 **개인 신원 확인을 할 담당자**. 여권 등 신분증 표기와 똑같이 적는다 |
|
||||
|
||||
4. **인증서 제목 미리 보기(Certificate subject preview)**로 인증서에 들어갈 문자열을 미리 본다. 이 값이 나중의 Publisher다.
|
||||
5. **만들기(Create)**. 상태가 **진행 중(In Progress)**이 된다.
|
||||
6. 상태가 **작업 필요(Action Required)**로 바뀌면 담당자 개인 확인 차례다. 기본 이메일로도 링크가 온다.
|
||||
1. 요청에 적은 이메일로 로그인해 링크를 연다.
|
||||
2. **신뢰할 수 있는 ID 검증 도구를 통해 여기에서 확인 받기(Get verified here through our trusted ID-verifiers)**를 누르면 외부 확인 업체(AU10TIX) 화면으로 간다.
|
||||
3. 이메일 PIN 확인 → 전화번호 입력 → **시작(Start)** → 노트북 화면의 QR 코드를 스마트폰으로 스캔.
|
||||
4. 스마트폰에서 여권·운전면허증·신분증 촬영과 안면 확인을 진행한다. 플래시 없이, 평평한 곳에서, 잘리지 않게 찍는다.
|
||||
5. **인증자 열기(Open Authenticator)**로 Microsoft Authenticator 앱에 확인된 ID(Verified ID)를 **추가(Add)**하고, 다시 브라우저 QR 코드를 스캔해 **공유(Share)**한다.
|
||||
6. 브라우저에 **확인 성공(Verification Successful)**이 뜨면 포털 상태가 몇 분 뒤 **진행 중**으로 돌아가 조직 확인이 이어진다.
|
||||
7. 서류 보완 메일이 오면 포털에서 업로드한다. 업로드 기회는 3번이다. 12개월 이내 발급, 만료가 2개월 이상 남은 서류여야 한다. 사업자등록증·법인등기부등본과 도메인 등록 증빙(회사명·도메인이 함께 적힌 것)을 준비해 둔다.
|
||||
8. 상태가 **완료(Completed)**가 되면 다음 단계로 간다. **실패(Failed)**면 사유 메일을 확인하고 새 요청을 만든다.
|
||||
|
||||
## 5단계. 인증서 프로필 만들기
|
||||
|
||||
1. 계정 화면 **개체 → 인증서 프로필(Certificate profiles) → 만들기(Create)**에서 **공용 신뢰(Public Trust)**를 고른다.
|
||||
2. **인증서 프로필 이름(Certificate Profile Name)**: 5~100자. 예: `baron-public-trust`
|
||||
3. **인증서 유형(Certificate Type)**은 자동 입력, **프로그램 유형(Program Type)**은 **없음(None)** 그대로 둔다.
|
||||
4. **유효성 검사된 CN 및 O(Verified CN and O)**에서 4단계에서 완료된 ID 유효성 검사를 선택한다. **거리 주소 포함(Include street address)**, **우편 번호 포함(Include postal code)**은 선택 사항이며 기본은 끈다.
|
||||
5. **인증서 제목 미리 보기(Certificate Subject Preview)**에 최종 주체가 보인다. 이 문자열을 **그대로 복사해 보관**한다. 예: `CN=Baron Software Co., Ltd., O=Baron Software Co., Ltd., L=Seoul, S=Seoul, C=KR`
|
||||
6. **만들기(Create)**.
|
||||
|
||||
CLI로는 ID 유효성 검사 화면에서 **ID 유효성 검사 ID**를 복사한 뒤 다음을 실행한다.
|
||||
|
||||
```powershell
|
||||
az artifact-signing certificate-profile create -g rg-codesigning --account-name baronsigning -n baron-public-trust --profile-type PublicTrust --identity-validation-id <ID 유효성 검사 ID>
|
||||
```
|
||||
|
||||
## 6단계. 서명 PC 준비
|
||||
|
||||
서명을 실행할 PC(개발 PC 또는 빌드 서버)에서 한다. Windows 10 1809 이상이어야 한다.
|
||||
|
||||
1. 관리자 PowerShell에서 클라이언트 도구를 설치한다. SignTool, .NET 8 런타임, 서명 플러그인(dlib)이 함께 들어온다.
|
||||
|
||||
```powershell
|
||||
winget install -e --id Microsoft.Azure.ArtifactSigningClientTools
|
||||
```
|
||||
|
||||
winget이 없는 서버는 MSI를 직접 받는다.
|
||||
|
||||
```powershell
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Invoke-WebRequest -Uri "https://download.microsoft.com/download/70ad2c3b-761f-4aa9-a9de-e7405aa2b4c1/ArtifactSigningClientTools.msi" -OutFile .\ArtifactSigningClientTools.msi
|
||||
Start-Process msiexec.exe -Wait -ArgumentList '/I ArtifactSigningClientTools.msi /quiet'
|
||||
Remove-Item .\ArtifactSigningClientTools.msi
|
||||
```
|
||||
|
||||
2. Windows SDK의 일반 SignTool은 이 플러그인과 호환되지 않는 버전일 수 있다. 설치된 도구 폴더의 SignTool을 쓴다. 기본 경로는 `C:\Program Files (x86)\Microsoft\ArtifactSigningClientTools\bin`이다.
|
||||
3. `metadata.json`을 만든다. Endpoint는 2단계의 계정 URI와 같아야 하며 지역이 다르면 403 오류가 난다.
|
||||
|
||||
```json
|
||||
{
|
||||
"Endpoint": "https://krc.codesigning.azure.net",
|
||||
"CodeSigningAccountName": "baronsigning",
|
||||
"CertificateProfileName": "baron-public-trust"
|
||||
}
|
||||
```
|
||||
|
||||
4. Azure CLI를 설치하고 서명자 역할이 있는 계정으로 로그인한다. 사람이 서명하는 PC에서는 이것으로 충분하다.
|
||||
|
||||
```powershell
|
||||
az login
|
||||
```
|
||||
|
||||
빌드 서버에서는 **Microsoft Entra ID → 앱 등록(App registrations)**에서 앱을 만들어 3단계의 서명자 역할을 주고 환경 변수로 인증한다. 비밀 값은 소스나 프로젝트 파일에 넣지 않는다.
|
||||
|
||||
```powershell
|
||||
$env:AZURE_TENANT_ID = "<테넌트 ID>"
|
||||
$env:AZURE_CLIENT_ID = "<앱 등록 클라이언트 ID>"
|
||||
$env:AZURE_CLIENT_SECRET = "<클라이언트 비밀>"
|
||||
```
|
||||
|
||||
## 7단계. MSIX 서명
|
||||
|
||||
서명 전에 패키지의 Publisher가 5단계의 주체와 글자 단위로 같아야 한다. 다르면 SignTool이 `0x8007000B`로 거부한다.
|
||||
|
||||
```powershell
|
||||
& "C:\Program Files (x86)\Microsoft\ArtifactSigningClientTools\bin\signtool.exe" sign /v /fd SHA256 `
|
||||
/tr "http://timestamp.acs.microsoft.com" /td SHA256 `
|
||||
/dlib "C:\Program Files (x86)\Microsoft\ArtifactSigningClientTools\bin\x64\Azure.CodeSigning.Dlib.dll" `
|
||||
/dmdf .\metadata.json `
|
||||
.\package.msix
|
||||
```
|
||||
|
||||
- `/tr`과 `/td`의 타임스탬프는 **반드시** 넣는다. 인증서가 3일 뒤 만료되므로 타임스탬프가 없으면 그 뒤 서명 검증이 실패한다.
|
||||
- 실패하면 `/debug`를 추가해 인증·인증서 체인 메시지를 본다.
|
||||
- dlib 경로는 설치 버전에 따라 `bin\x64\` 또는 `bin\` 바로 아래일 수 있다. 폴더에서 `Azure.CodeSigning.Dlib.dll` 위치를 확인한다. 32비트 SignTool에는 x86 dlib를 쓴다.
|
||||
|
||||
서명 결과 확인:
|
||||
|
||||
```powershell
|
||||
signtool verify /pa /v .\package.msix
|
||||
Get-AuthenticodeSignature .\package.msix | Format-List Status, SignerCertificate, TimeStamperCertificate
|
||||
```
|
||||
|
||||
`Status`가 Valid이고 SignerCertificate의 Subject가 5단계 값과 같으면 된다. 이 PC에 자체 서명 테스트 인증서를 신뢰 등록한 적이 있어도 운영 서명 검증과는 무관하다.
|
||||
|
||||
## 8단계. Installer Studio에 반영
|
||||
|
||||
1. 앱 정보 탭 **패키지 identity → Publisher**에 5단계에서 복사한 주체를 그대로 붙인다. 쉼표 뒤 공백까지 같아야 한다.
|
||||
2. Identity Name은 바꾸지 않는다. Publisher가 바뀌면 PackageFamilyName이 달라져 기존 테스트 설치본과는 다른 앱이 된다. 테스트용 자체 서명본을 설치한 PC에서는 먼저 제거한다.
|
||||
3. 현재 Installer Studio의 서명 모드는 `unsigned`와 `development`(자체 서명)만 있다. 운영 서명은 **미서명 모드로 빌드한 뒤 7단계 명령을 수동으로 실행**하는 방식으로 진행한다. 제작기 안에서 `/dlib` 호출을 지원하는 운영 서명 프로필은 백로그 BL-03 후속 작업이다.
|
||||
4. 서명본은 제작기의 `releases/<id>` 폴더를 덮어쓰지 말고 별도 출력 폴더에 두고, 서명 로그와 `signtool verify` 결과를 함께 보관한다.
|
||||
|
||||
## 자주 막히는 곳
|
||||
|
||||
| 증상 | 확인할 것 |
|
||||
| --- | --- |
|
||||
| **새 ID** 버튼이 흐림 | 3단계 ID 검증 도구 역할이 없거나 구독 읽기 권한자가 없다 |
|
||||
| 신원 확인 메일이 안 옴 | 기본 이메일이 외부 발신 링크를 받는지, 7일이 지나지 않았는지. 지났으면 새 요청 |
|
||||
| 403 Forbidden / `SignerSign()` 실패 | metadata.json의 Endpoint 지역이 계정 지역과 다르거나 서명자 역할이 없다 |
|
||||
| `0x8007000B` | Publisher와 인증서 주체 불일치 |
|
||||
| dlib 로드 실패 | SDK 기본 SignTool을 쓴 경우. 클라이언트 도구 폴더의 SignTool 사용 |
|
||||
| 서명 3일 뒤 검증 실패 | 타임스탬프 없이 서명함. 다시 서명 |
|
||||
| 설치 PC에서 "신뢰할 수 없는 게시자" | Windows 10 1809 미만, 또는 루트 업데이트가 없는 격리 PC |
|
||||
| 요금이 나가는데 아직 못 씀 | 정상. 계정 생성일부터 과금되므로 신원 확인 서류를 먼저 모은 뒤 계정을 만든다 |
|
||||
|
||||
## Key Vault + 공인 CA 방식 (대안)
|
||||
|
||||
아티팩트 서명을 쓸 수 없거나 특정 CA 인증서가 필요할 때의 방법이다. 비용은 CA 인증서 가격이 더해지고, 설정도 더 많다.
|
||||
|
||||
1. **키 자격 증명 모음(Key Vault)**을 **프리미엄(Premium)** 가격 책정 계층으로 만들고 **개체 → 인증서(Certificates) → 생성/가져오기(Generate/Import)**에서 **인증서 발급 방법(Method of Certificate Creation)**을 "생성", **인증 기관 유형(Type of Certificate Authority)**을 "통합되지 않은 CA에서 발급한 인증서(Certificate issued by a non-integrated CA)"로 고른다. 고급 정책에서 키 종류 RSA-HSM, 키 크기 3072 이상, **내보내기 가능한 프라이빗 키**는 아니오, 확장 키 사용(EKU)에 코드 서명 `1.3.6.1.5.5.7.3.3`을 넣는다.
|
||||
2. 생성된 인증서 항목에서 **인증서 작업(Certificate Operation) → CSR 다운로드(Download CSR)**로 요청 파일을 받아 CA(DigiCert, Sectigo, GlobalSign 등)에 OV 코드 서명 인증서로 신청한다. CA가 조직 확인을 한다. **CA가 Azure Key Vault 키 증명을 받아 주는지 먼저 문의**한다. 2023년 이후 규정으로 HSM 보관 증명이 필요하다.
|
||||
3. 발급된 인증서를 **서명된 요청 병합(Merge Signed Request)**으로 합친다.
|
||||
4. 서명 계정에 **액세스 제어(IAM)**에서 **Key Vault 암호화 사용자(Key Vault Crypto User)**와 **Key Vault 인증서 사용자(Key Vault Certificate User)** 역할을 준다.
|
||||
5. 서명 PC에 `dotnet tool install --global AzureSignTool`을 설치하고 서명한다.
|
||||
|
||||
```powershell
|
||||
azuresigntool sign --azure-key-vault-url https://<vault>.vault.azure.net `
|
||||
--azure-key-vault-certificate <인증서 이름> --azure-key-vault-managed-identity `
|
||||
--timestamp-rfc3161 http://timestamp.digicert.com --timestamp-digest sha256 --file-digest sha256 `
|
||||
.\package.msix
|
||||
```
|
||||
|
||||
사람이 서명할 때는 `--azure-key-vault-managed-identity` 대신 `az login` 뒤 `--azure-key-vault-tenant-id`, `--azure-key-vault-client-id`, `--azure-key-vault-client-secret`(앱 등록) 조합을 쓴다.
|
||||
6. 인증서 주체를 Publisher에 복사하는 8단계는 동일하다. 갱신 때 CA·회사 정보 표기를 바꾸지 않아야 주체가 유지된다.
|
||||
|
||||
## 근거
|
||||
|
||||
- [빠른 시작: 아티팩트 서명 설정 (한국어)](https://learn.microsoft.com/ko-kr/azure/artifact-signing/quickstart) — 한국어 메뉴명, 지원 국가, 지역·엔드포인트, ID 유효성 검사 절차, 인증서 프로필
|
||||
- [자습서: 아티팩트 서명에서 역할 할당 (한국어)](https://learn.microsoft.com/ko-kr/azure/artifact-signing/tutorial-assign-roles) — 역할 이름과 권한 범위
|
||||
- [Set up signing integrations to use Artifact Signing](https://learn.microsoft.com/en-us/azure/artifact-signing/how-to-signing-integrations) — 클라이언트 도구 설치, metadata.json, SignTool 명령, 타임스탬프
|
||||
- [Sign your MSIX package - end-to-end guide](https://learn.microsoft.com/windows/msix/package/sign-msix-package-guide) — MSIX 운영 서명 절차, 지원 Windows 버전, SmartScreen 안내
|
||||
- [Artifact Signing pricing](https://azure.microsoft.com/en-us/pricing/details/artifact-signing/) — Basic/Premium 포함 서명 횟수, 과금 시작 시점
|
||||
@@ -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 설치·업데이트 확인 결과:
|
||||
```
|
||||
@@ -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의 기존 설치 기반을 보존하면서 네트워크 배포를 추가한다. 설치 도구 변경과 앱 구조 변경을 한 번에 강제하지 않는 방향으로 검토한다.
|
||||
|
||||
## 확정 요구사항
|
||||
|
||||
@@ -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 채택과 도구 버전·이용 조건은 구현 시작 전에 확정한다.
|
||||
|
||||
## 대체 도구 검토
|
||||
|
||||
@@ -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) 참조.
|
||||
|
||||
패키지는 등록 후 변경하지 않고 배포 정책만 변경한다. 같은 릴리스를 개발자 테스트부터 일반 배포까지 사용하며, 수정이 필요하면 새 릴리스를 만든다.
|
||||
|
||||
## 버전 및 관리 정보
|
||||
|
||||
@@ -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) 참조.
|
||||
|
||||
하나의 릴리스를 네 그룹에 순차적으로 공개한다. 초기에는 담당자가 관측 결과를 확인한 후 다음 단계를 승인하는 방식을 권고한다.
|
||||
|
||||
## 그룹 구성
|
||||
|
||||
@@ -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)를 따른다.
|
||||
|
||||
## 구현 전 결정할 항목
|
||||
|
||||
| 항목 | 현재 상태 | 필요한 결정·확인 |
|
||||
|
||||
@@ -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) 참조.
|
||||
|
||||
버전별 패치는 필수 요구사항이다. 현재 설치된 버전과 상태를 확인해 변경분만 적용할 수 있어야 하며, 신규 설치·최초 설치기 전환·패치 비호환 상태를 위한 전체 설치 패키지도 함께 제공한다.
|
||||
|
||||
## 권장 구현 방식
|
||||
|
||||
@@ -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 지원 없이 단독 개발**하는 기준이며, 제작기·업데이터·서버 작업을 한 사람의 용량 안에서 순차 배치한다. 기존 문서의 단계 목록은 영역별 설계 설명으로 유지하고, 통합 실행 순서와 통과 조건은 이 일정 문서를 기준으로 추적한다.
|
||||
|
||||
외부 승인·테스터 모집·서명 계약의 실제 담당자, 지원 버전 목록, 주당 확보 가능한 시간은 아직 확정되지 않았다. 이 문서는 해당 결정을 대신 승인하지 않는다.
|
||||
@@ -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차에 넣으면 설계·백로그·일정을 함께 변경한다. 사용자 요청 없이 후속 개발 일정을 확정하지 않는다.
|
||||
@@ -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는 후속 범위로 유지한다. 적용 가능한 기준 버전과 설치 상태를 검증한 뒤 패치를 실행한다.
|
||||
|
||||
## 검토 범위
|
||||
|
||||
|
||||
@@ -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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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>
|
||||
@@ -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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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"><Identity\n Name="${esc(s.identity)}"\n Publisher="${esc(s.publisher)}"\n Version="${esc(s.version)}"\n ProcessorArchitecture="${esc(s.arch)}" /></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>
|
||||
@@ -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/
|
||||
@@ -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" }
|
||||
@@ -0,0 +1,177 @@
|
||||
# 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` 파일을 연 뒤 설치 앱 정보(표시 정보·패키지 identity) → 설치 구성 › 설치파일에서 소스 폴더 연결 → 검증·빌드에서 SDK 도구 선택 → 입력 검사 → 빌드 계획 생성 → MSIX 빌드 순서로 사용한다. 폴더·파일은 설치파일 트리에서 추가·삭제한다. `**/*`·제외 패턴 같은 규칙을 직접 고치려면 앱을 닫은 상태에서 버전 파일(`.isver`)을 수정한다. 화면에 없는 설정(의존성, 추가 소스 alias 등)은 앱을 닫은 상태에서 `.isproj` 파일을 직접 수정한다. 편집 화면에는 JSON 편집기와 하단 상태 바가 없으며 오류는 편집 영역 위에 표시한다. 설치파일 하위 탭은 InstallShield의 Files and Folders 보기처럼 왼쪽에 **대상 컴퓨터** 폴더 트리, 오른쪽에 선택한 폴더의 파일 규칙과 패키지 파일을 보여준다. 트리 루트는 앱 설치 폴더(INSTALLDIR에 해당하는 패키지 루트, `C:\Program Files\WindowsApps\<PackageFullName>`) 하나다. VFS 폴더는 사용하지 않으며, 기존 프로젝트의 `VFS/...` 규칙 대상은 경고와 함께 표시해 앱 설치 폴더 아래로 옮기게 한다. 빌드 단계의 VFS 경로 거부는 아직 구현하지 않았다. 폴더는 파일 규칙의 `targetRelativePath`로만 저장되므로 새 하위 폴더는 규칙을 추가해야 프로젝트에 남는다. 규칙 ID는 `files-<UUID>`로 자동 생성하며 화면에 표시하지 않는다. 사용자 AppData는 VFS가 없어 안내만 표시한다. 기본 프로젝트의 Publisher는 예시이며 자신의 패키지 identity에 맞게 바꿔야 한다.
|
||||
|
||||
시작 화면은 왼쪽 **최근 프로젝트 목록**, 오른쪽 **프로젝트 열기 / 새 EXE 프로젝트 만들기 / 새 MSIX 프로젝트 만들기(구현 예정, 비활성)** 버튼으로 구성한다. 프로젝트 유형은 `.isproj`의 `formats`에 `msix`와 `exe` 중 무엇이 있는지로 정하며, 열기와 최근 목록은 파일의 유형에 맞는 작업 화면을 연다. 둘 다 있거나 둘 다 없는 파일은 `InvalidProjectKind`로 열지 않고, 저장 시 유형이 바뀐 프로젝트(파일 직접 수정 포함)는 `ProjectKindChanged`로 거부한다. 편집 화면에는 새로 만들기·열기 버튼과 패키지 개요 패널이 없으며, 다른 프로젝트는 프로젝트를 닫은 뒤 시작 화면에서 열거나 만든다.
|
||||
|
||||
**EXE 프로젝트**는 버전별 메타파일로 관리한다. `EG-BIM.isproj`에는 모든 버전이 공유하는 설정(앱 이름·게시자, 아키텍처 x64·x86, `upgradeCode`, 기본 설치 폴더, 설치 범위, 속성문자, 소스 alias)만 두고 `files`·`registry`는 비워 둔다(`VersionedContent`). 버전마다 `EG-BIM.versions/<버전>.isver`(JSON, 스키마 `schemas/exe-version.schema.json`) 파일 하나에 제품 버전(Major.Minor.Patch, 파일 이름과 같아야 함 — `VersionFileMismatch`), `productCode`, 잠금 여부, 설치파일 규칙, 레지스트리, 바로가기를 둔다. 새 EXE 프로젝트는 `1.0.0.isver`로 시작한다. 이전 단일 버전 EXE 형식(`formats.exe`의 `msiVersion`·`productCode`·`shortcuts`)은 자동 변환하지 않고 열기를 거부한다.
|
||||
|
||||
설치 앱 정보에서는 공유 설정(앱 이름·게시자·아키텍처·UpgradeCode)을 편집한다. arm64는 EXE에서 `UnsupportedArchitecture`로 거부한다. 새 EXE 프로젝트는 버전 없이 시작한다. **설치 구성**은 항상 **버전 목록**(표: 버전·종류·ProductCode·설명, 표 오른쪽 바깥: 눌러서 잠그기/잠금 해제하는 자물쇠 버튼, 저장 전 표시, 휴지통 삭제 버튼)을 먼저 보여 준다. 버전이 없으면 목록에 "*기존 버전이 없습니다. 신규버전을 만드세요."가 표시된다. 목록 오른쪽 위 **버전 추가**는 팝업을 열고, **만들기**/**취소**로 확정한다. 버전이 없을 때는 버전 번호만 입력하며 빈 내용의 메이저 첫 버전이 만들어진다. 목록에서 버전 번호를 누르면 그 버전의 일반·설치파일·레지스트리·바로가기·설치화면 탭이 열리고, 위쪽 막대의 버전 번호 왼쪽 화살표 버튼이나 사이드바의 **설치 구성**을 다시 누르면 버전 목록으로 돌아간다. 일반 탭의 **설명**에는 이 버전의 변경 내용이나 출시 메모(1000자 이하, `DescriptionTooLong`)를 남기며 목록의 설명 열에 표시된다. 버전을 복사해 추가하면 설명은 비어 있다. 버전이 있으면 팝업에서 새 버전 번호, 종류(**패치** / **메이저**, 버전 번호에 맞춰 자동 선택: 아래쪽 메이저 버전과 Major·Minor가 같으면 패치, 아니면 메이저. 직접 바꿀 수 있지만 아래에 메이저 버전이 없는 최초 버전은 패치를 고를 수 없다), 복사할 버전(기본: 마지막으로 연 버전)을 지정한다. 잘못된 번호·중복·메이저 버전 없음은 팝업 안에 오류로 표시하고 팝업을 닫지 않는다. 새 버전은 복사한 버전의 설치파일·레지스트리·바로가기를 그대로 가지며 잠금이 풀린 상태로 시작한다. 메이저 버전은 새 ProductCode를 받고, 패치 버전은 아래쪽 메이저 버전의 ProductCode를 이어받는다(낮은 메이저 버전이 없으면 `MissingMajorVersion`). 패치 버전은 출시 시 전체 설치 파일과, 같은 라인의 이전 버전을 한 번에 올리는 누적 패치 파일을 만들 예정이다([15 EXE 버전과 누적 패치](../docs/code-design/15-exe-versions-and-patches.md)). 입력 검사는 패치가 올릴 이전 버전의 잠금(`BaselineNotLocked`), ProductCode(`PatchProductCodeMismatch`, 메이저 사이의 `ProductCodeReused`), 이전 버전 내용의 제거·이동(`PatchRemovesFiles`, `PatchMovesFiles`, `PatchRemovesRegistry`, `PatchRemovesShortcut`)을 검사한다. MSI·MSP 생성은 아직 구현하지 않았다. 열린 버전 위쪽 막대 오른쪽 끝의 **잠금** / **잠금 해제** 버튼(현재 상태가 눌린 상태로 표시)으로 배포한 버전을 보호한다. 잠긴 버전은 화면이 읽기 전용이고, 저장 시에도 내용 변경을 `VersionLocked`로 거부한다(잠금 해제만 먼저 저장한 뒤 수정). 표 오른쪽 바깥의 휴지통 아이콘(**삭제**)은 확인 팝업 후 버전을 목록에서 지우고, 저장할 때 버전 메타파일을 `<버전>.isver.bak`으로 옮긴다(이름을 되돌리면 복구된다). 잠긴 버전은 휴지통이 보이지 않으며, 자물쇠를 눌러 잠금을 해제하면 나타난다(저장 전에 해제와 삭제를 함께 저장해도 된다). 패치 버전이 ProductCode를 이어받는 메이저 버전(`VersionInUse`, 패치를 먼저 삭제)은 삭제 버튼이 비활성화되고 저장할 때도 거부된다. 저장은 바뀐 버전 파일만 쓰며 기존 파일은 `.isver.bak`으로 보관하고, 다른 곳에서 바뀐 버전 파일은 `RevisionConflict`로 거부한다. 입력 검사와 검증·빌드 탭은 선택한 버전을 검사한다. 사이드바 하단 환경설정 위의 **프로젝트 설정 › 속성문자** 탭에서 `[INSTALLDIR]`, `[ProductName]`, `[ProgramFiles64Folder]` 등 기본 속성(읽기 전용, 값은 예시)을 확인하고 사용자 속성(`formats.exe.properties`, 이름은 대문자·숫자·`_`·`.`)을 추가한다. 레지스트리 키 경로·값 이름·문자열 데이터와 속성 값에 `[이름]`을 적으면 설치할 때 치환되며, 대괄호 문자는 `[\[]`, `[\]]`로 적는다. 입력 검사는 정의되지 않은 속성(`UnknownProperty`), 잘못된 이름(`InvalidPropertyName`), 기본 속성과 같은 이름(`ReservedProperty`), 중복(`DuplicateProperty`), 순환 참조(`CircularProperty`), 닫히지 않은 대괄호·미지원 `[#]`/`[$]`/`[!]`/`[~]` 형식(`InvalidFormattedText`)을 거부한다. 실제 치환은 EXE 빌드(MSI)에서 수행하며 EXE 빌드는 아직 구현하지 않았다. MSIX는 속성을 치환하지 않는다.
|
||||
|
||||
**설치 구성 › 일반** 탭(EXE 전용, EXE 프로젝트를 열면 처음 보이는 탭)은 선택한 버전의 제품 버전(읽기 전용), ProductCode(**새 GUID** 가능, UpgradeCode와 달라야 함 — `DuplicateProductCode`, 패치 버전은 읽기 전용), 버전 종류(메이저/패치), 설명을 보여 준다. 잠금 상태는 위쪽 막대의 **잠금** / **잠금 해제** 버튼으로 바꾼다. MSIX의 패키지 버전은 설치 앱 정보의 identity에서 편집한다.
|
||||
|
||||
**설치 구성 › 설치파일** 탭에는 설치 폴더(`INSTALLDIR`)와 추가한 Windows 기본 폴더 아래의 폴더·설치될 파일을 보여 주는 탐색기형 트리(테두리 없음)와, 탭 설명 줄 오른쪽 끝의 **설치 위치 추가**(EXE만) 버튼만 있다(오류가 있을 때만 경고 문구가 붙는다). 루트 행(기본 설치 폴더·Windows 기본 폴더)에는 이름 옆에 전체 설치 경로(예: `C:\Program Files\Baron\EG-BIM`, `C:\ProgramData`)가 보이고, 하위 폴더·파일은 마우스를 올리면 전체 경로가 나온다. 폴더 행 오른쪽에는 폴더 추가·파일 추가·삭제 아이콘 버튼이(MSIX의 앱 설치 폴더는 삭제 없음), 파일 행에는 삭제 버튼만 있다. 새 첫 버전은 설치 폴더 행과 "*설치할 파일들을 추가하세요." 문구만 보이고, 복사로 만든 버전은 복사한 버전과 같은 파일이 보인다. 파일 목록은 저장하지 않아도 화면의 규칙으로 소스 폴더를 읽어(`preview_files`, 해시 계산 없음) 표시하며, 읽지 못한 규칙은 트리 위에 오류로 보여 준다. 폴더 행을 누르면 접히거나 펼쳐진다. 폴더 행의 **폴더 추가**는 팝업에서 이름을 받아 그 폴더 아래에 만들며 `Data\Config`처럼 여러 단계를 한 번에 만들 수 있다(파일을 넣어야 저장됨). **설치 위치 추가**는 기본 설치 폴더와 Windows 기본 폴더(Program Files·Program Files (x86)·ProgramData·사용자 AppData/LocalAppData — x86은 64비트 폴더 제외)를 나열하고(이미 추가된 위치는 "추가됨"으로 비활성화), 고른 위치를 트리에 루트로 더한다. **직접 입력…**으로 `[CommonAppDataFolder]Baron\Config`처럼 `[속성]하위\경로`(속성 없으면 기본 설치 폴더 기준)를 적을 수도 있다. 기본 설치 폴더도 삭제하면 그 안의 규칙과 함께 트리에서 빠지고, 설치 위치 추가로 다시 넣을 수 있다. Windows 기본 폴더는 트리에 별도 루트로 나타나고 규칙의 `targetRoot`로 저장되며, 허용되지 않은 위치는 `UnsupportedTargetRoot`로 거부한다. 사용자 AppData 폴더는 설치한 사용자에게만 만들어진다. 폴더 행의 **파일 추가**는 파일 선택 창(`choose_files`)에서 고른 파일을 그 폴더 바로 아래에 넣는다. 이 PC에 연결된 소스 폴더가 없으면 고른 파일이 있는 폴더를 첫 소스의 폴더로 연결한다(드라이브 루트는 `RootSource`로 거부, 저장해야 로컬 설정에 반영). 이후 고른 파일은 연결된 소스 폴더 안에 있어야 하며(밖이면 연결된 폴더를 알려 주는 `OutsideSource`), 같은 소스 폴더의 파일끼리 `sourceFolder`가 그 폴더이고 포함 패턴이 파일 이름인 규칙으로 저장된다. 규칙의 소스 기준 폴더가 없으면 `MissingSourceFolder`로 거부한다. 연결된 소스 폴더를 바꾸는 화면은 아직 없다. 행에 마우스를 올리면 나오는 휴지통으로 파일·폴더(추가한 Windows 기본 폴더 포함)를 설치 목록에서 뺀다(소스 폴더의 실제 파일은 지우지 않음). 파일 추가로 넣은 파일은 규칙에서 그 이름을 지우고(빈 규칙은 삭제), `**/*` 같은 패턴으로 들어온 파일은 그 규칙의 제외 패턴에 넣는다. 폴더는 확인 팝업 뒤 그 폴더와 하위를 대상으로 하는 규칙을 지우고, 위쪽 폴더의 패턴 규칙에는 `폴더/**` 제외를 더한다. 설치 폴더 자체는 지울 수 없고, 파일 목록을 읽는 중에는 `NotScanned`로 막는다.
|
||||
|
||||
**설치 구성 › 바로가기** 탭은 선택한 EXE 버전의 바로가기(`shortcuts`)를 그리드(위치·폴더·이름·대상 파일·인수)로 편집한다. 위치는 시작 메뉴(`programMenu`, 하위 폴더 지정 가능), 바탕화면(`desktop`), 시작 프로그램(`startup`)이다. 대상은 설치 폴더 기준 `/` 경로이며 입력 검사 후 설치될 `.exe`가 추천 목록에 나온다. 입력 검사는 위치(`UnsupportedShortcutLocation`), 폴더(`InvalidShortcutFolder`), 이름(`InvalidShortcutName`), 대상 경로(`InvalidShortcutTarget`), 같은 위치·폴더·이름 중복(`DuplicateShortcut`)을 거부하고, 파일 규칙으로 설치되지 않는 대상은 `MissingShortcutTarget`으로 거부한다. 인수에는 속성문자를 쓸 수 있다. MSIX는 실행 항목으로 시작 메뉴를 자동 생성하므로 바로가기를 따로 편집하지 않는다. 기본 설치 폴더(`installRoot`, 예: `[ProgramFiles64Folder]Baron\EG-BIM`)와 설치 범위(`perMachine`만)는 화면에 없으며 `.isproj` 파일에서 편집한다. 설치파일 트리의 루트는 기본 설치 폴더이며 MSIX 전용 앱 데이터 안내와 VFS 경고는 표시하지 않는다. 레지스트리 설정은 저장만 된다. **설치 구성 › 설치화면** 탭은 선택한 버전의 설치 단계와 화면을 편집한다. 왼쪽 단계 목록에서 약관·안내·입력 화면을 **단계 추가**로 넣고(설치 확인 앞), ▲▼로 순서를 바꾸고, 추가한 단계는 ×로 지운다. 기본 화면(시작·설치 폴더·설치 확인·진행·완료·유지 관리·제거 확인·실패·취소)은 지울 수 없고 시작·설치 폴더·안내·입력 화면만 숨길 수 있다. 오른쪽에서 선택한 화면의 제목·설명(속성문자 사용 가능), 약관 파일, 설치 폴더 고정, 입력 항목(사용자 속성·이름·형식·기본값·선택 목록·필수)을 편집한다. 아래에서 밝기·강조 색·로고와 스크립트(이름, 실행 시점: 파일 복사 전·후/제거 전/완료 후 사용자, 종류: PowerShell·CMD·EXE, 소스와 파일 경로, 한 줄에 하나인 인수, 실패 시 중단/무시, 제한 시간)를 편집한다. 입력 검사는 화면 순서·숨김(`InvalidPageOrder`, `PageCannotBeHidden`), 누락 화면(`MissingPage`), 약관 파일(`MissingLicenseFile`), 입력 항목(`UnknownProperty`, `DuplicateInputProperty`, `InvalidChoice`), 스크립트(`UnknownSource`, `InvalidScriptPath`, `ScriptKindMismatch`, `ScriptFailureAfterFinish`, `InvalidScriptTimeout`)와 실제 파일(`MissingScriptFile`, `MissingAsset`)을 검사한다. 화면 표시와 스크립트 실행은 EXE 빌드와 함께 구현하며 지금은 설정만 저장·검사된다. EXE 프로젝트의 사이드바에는 업데이트 단계가 없다(설치 앱 정보·설치 구성·서명·검증 · 빌드). 업그레이드·패치 방식은 설치 구성의 버전 종류로 정한다. **EXE 빌드·서명·업데이트는 아직 구현하지 않았다.** 입력 검사(파일 규칙 해석 포함)까지 사용할 수 있고, 빌드 계획 생성은 `UnsupportedFormat`, EXE 프로젝트의 개발자 서명 모드는 `UnsupportedSigning`으로 거부한다. 최근 목록은 이 버전의 GUI에서 열거나 만든 프로젝트를 최대 20개 보관하며 앱을 다시 실행해도 유지된다. 이름·경로 검색과 오늘/이번 주/이번 달/이전 그룹을 제공한다. 목록의 `×`는 기록만 제거하며 프로젝트 파일을 삭제하지 않는다. 이동·삭제된 파일은 위치 확인 안내를 표시한다.
|
||||
|
||||
편집 화면 상단의 **프로젝트 닫기**로 시작 화면에 돌아간다. 미저장 변경이 있으면 **저장 후 닫기 / 저장 안 함 / 취소**를 선택하며, 저장 오류가 발생하면 프로젝트와 편집 내용을 유지한다. 창을 닫을 때도 같은 보호 절차를 적용한다. 빌드 중에는 프로젝트를 닫을 수 없다.
|
||||
|
||||
최근 목록은 Tauri의 앱 데이터 폴더(기본 Windows 경로 `%APPDATA%\dev.networksetup.msixstudio\recent-projects.json`)에 저장한다. 프로젝트 경로·표시명·최근 열기 시각만 기록한다. 목록 저장 실패가 프로젝트 열기 자체를 차단하지는 않는다.
|
||||
|
||||
사이드바 하단의 **환경설정**은 프로그램 전체에 적용되는 설정을 바꾼다. 테마는 **어둡게**(기본)·**밝게**·**Windows 설정 따르기** 중에서 고르며 바로 적용된다. 표시 언어는 현재 한국어만 제공하고 English는 번역 전까지 선택할 수 없다. 환경설정은 데스크톱 WebView의 로컬 저장소(`installer-studio.preferences`)에 테마·언어 값만 저장하며 프로젝트 파일에는 기록하지 않는다. 저장된 값이 손상되었거나 알 수 없는 항목이 있으면 `InvalidPreferences` 경고와 함께 기본값으로 열고, 저장에 실패하면 `PreferencesNotSaved`로 알리고 이번 실행에만 적용한다.
|
||||
|
||||
프런트엔드만 `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 ...`로 명시 실행한다.
|
||||
|
||||
## 레지스트리 설정
|
||||
|
||||
**설치 구성 › 레지스트리** 탭에서 값을 추가·수정·삭제한 뒤 저장하고 새 빌드 계획을 생성한다. 값은 한 줄에 한 항목인 그리드(루트·키 경로·값 이름·형식·데이터)로 편집한다. 32/64비트 뷰는 따로 고르지 않는다. 값은 프로젝트 아키텍처의 위치(x64·arm64는 64비트, x86은 32비트)에 기록하고, 64비트 프로젝트에서 32비트 위치가 필요하면 키 경로에 `Software\WOW6432Node\…`(HKCR은 `WOW6432Node\…`)를 적는다. 이 노드는 저장할 때 키에서 빼고 프로젝트 파일의 `view: "32"`로 기록하며 화면에는 다시 경로로 보여 준다. 아키텍처를 바꾸면 기본 위치를 따르던 항목의 `view`도 함께 바뀐다. 루트는 MSIX 프로젝트에서 HKLM(`Software` 아래)만, EXE 프로젝트에서 HKLM·HKCU·HKCR을 선택할 수 있으며 다른 루트와 형식에 맞지 않는 키는 `UnsupportedRegistryHive`·`InvalidRegistryKey`로 거부한다. EXE의 키 경로는 `Software`로 시작하지 않아도 된다. `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 --kind msix
|
||||
cargo run -p package-builder -- init --project D:\packages\tool\ToolSetup.isproj --kind exe
|
||||
cargo run -p package-builder -- validate --project D:\packages\demo\InstallerProject.isproj
|
||||
cargo run -p package-builder -- validate --project D:\packages\tool\ToolSetup.isproj --version 1.0.0
|
||||
cargo run -p package-builder -- version-schema
|
||||
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를 완료로 표시하지 않는다.
|
||||
@@ -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"
|
||||
@@ -0,0 +1,189 @@
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand, ValueEnum};
|
||||
use package_domain::ProjectKind;
|
||||
use package_infra as infra;
|
||||
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,
|
||||
#[arg(long, value_enum)]
|
||||
kind: Kind,
|
||||
},
|
||||
Validate {
|
||||
#[arg(long)]
|
||||
project: PathBuf,
|
||||
/// EXE projects validate one version, e.g. 1.14.0.
|
||||
#[arg(long = "version")]
|
||||
product_version: Option<String>,
|
||||
},
|
||||
/// JSON Schema of the EXE version meta file (`*.isver`).
|
||||
VersionSchema,
|
||||
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(Clone, Copy, ValueEnum)]
|
||||
enum Kind {
|
||||
Msix,
|
||||
Exe,
|
||||
}
|
||||
#[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, kind } => {
|
||||
let kind = match kind {
|
||||
Kind::Msix => ProjectKind::Msix,
|
||||
Kind::Exe => ProjectKind::Exe,
|
||||
};
|
||||
print(&infra::create_project(
|
||||
&std::path::absolute(project)?,
|
||||
kind,
|
||||
)?)?
|
||||
}
|
||||
Action::Validate {
|
||||
project,
|
||||
product_version,
|
||||
} => {
|
||||
let doc = infra::open_project(&project)?;
|
||||
let report = infra::validate_document(&doc, product_version.as_deref())?;
|
||||
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::VersionSchema => print(&schemars::schema_for!(package_domain::ExeVersion))?,
|
||||
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);
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
@@ -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>
|
||||
@@ -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.
|
||||
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 66 KiB |
@@ -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"]
|
||||
@@ -0,0 +1,24 @@
|
||||
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",
|
||||
"choose_files",
|
||||
"preview_files",
|
||||
"validate_project",
|
||||
"developer_certificate",
|
||||
"plan_release",
|
||||
"start_build",
|
||||
"cancel_build",
|
||||
"export_release",
|
||||
]),
|
||||
))
|
||||
.expect("Tauri build setup failed");
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"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-unmaximize",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-title",
|
||||
"core:window:allow-center",
|
||||
"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-choose-files",
|
||||
"allow-preview-files",
|
||||
"allow-validate-project",
|
||||
"allow-plan-release",
|
||||
"allow-start-build",
|
||||
"allow-cancel-build",
|
||||
"allow-export-release",
|
||||
"allow-developer-certificate"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 19 KiB |