feat: add EXE projects with versioned install configuration
- EXE project kind next to MSIX: formats.exe, new EXE projects, EXE app info (architecture, UpgradeCode), per-kind workspace; EXE builds and signing are still refused explicitly (#9). - Install configuration tabs: registry grid, shortcuts and installer properties with formatted-text checks (#10). - Per-version .isver files: version list, add dialog, lock/unlock, delete with .isver.bak backups, description (#11). - Major/patch versions with cumulative patch checks and automatic kind selection (#12). - 설치화면 tab: setup steps, theme and scripts per version (#13). - Preferences: theme and display language (#14). - 설치파일 tab as an Explorer-style tree: per-row add/remove, Windows folder install locations (FileRule.targetRoot), picked files placed with FileRule.sourceFolder, unsaved-rule file preview (#17). Checks: cargo fmt/clippy/test, 82 frontend tests, Vite build and the msix-studio build pass. npm ci, GUI click tests and real EXE builds were not run. Refs #9 #10 #11 #12 #13 #14 #17 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
905e6d4eaa
commit
366c8fe173
69 files changed
+9295
-441
No files matched your search
@@ -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,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개 통과.
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
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 입력으로 보존하고 백업 후 명시적으로 가져온다.
|
||||
@@ -38,7 +49,7 @@ project/
|
||||
| PackageProject v2 | schemaVersion, product, sources, files, formats, targets; 형식별 입력 분리. 별도 프로젝트 ID 없음 |
|
||||
| InstallationFlow / InstallerUi | 단계 의존성·모드·조건, 테마/페이지/바인딩. [상세 계약](09-install-flow-and-ui.md) 참조 |
|
||||
| ProductSpec | displayName, manufacturer, displayVersion; 제품별 선택 메타데이터. 설치 범위·GUID는 formats.exe, MSIX identity는 formats.msix |
|
||||
| FileRule | 고유 id, source alias, include/exclude 패턴, targetRelativePath, required; v1 destinationDirectory는 형식별 루트와 상대 경로로 변환 |
|
||||
| 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 | 형식별 계열. EXE: ProductCode·컴포넌트 GUID·tombstone; MSIX: Name·Publisher·Architecture·버전 이력 |
|
||||
|
||||
@@ -8,17 +8,28 @@
|
||||
|
||||
예: `EG-BIM.isproj`, `BaronViewer.isproj`. MSIX 전용 확장자로 제한하지 않으며 후속 EXE/MSI 제작에도 같은 형식을 사용한다. 최종 배포 파일인 `.msix`, `.appinstaller`, `.exe`, `.msi`, `.msp`와 구분한다.
|
||||
|
||||
[MSIX 프로젝트 예제](examples/msix.isproj)는 기존 v2 예제의 내용을 유지하고 확장자만 적용한 설계용 파일이다. 미확정 값이 포함되어 있으므로 그대로 배포용 패키지를 만들 수 있다는 의미는 아니다.
|
||||
[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 # 직전 로컬 설정 저장본
|
||||
@@ -38,7 +49,7 @@ project/
|
||||
|
||||
| 동작 | 설계 규칙 |
|
||||
| --- | --- |
|
||||
| 새 프로젝트 | 기본 파일명 `InstallerProject.isproj`, 필터 `Installer Studio 프로젝트 (*.isproj)` |
|
||||
| 새 프로젝트 | 시작 화면의 새 MSIX/EXE 프로젝트 버튼 또는 CLI `init --kind msix|exe`. 기본 파일명 `InstallerProject.isproj`, 필터 `Installer Studio 프로젝트 (*.isproj)` |
|
||||
| 프로젝트 열기 | `.isproj` 전용 필터. 기존 `.json` 파일은 공통 로더에서도 거부한다. |
|
||||
| 확장자 처리 | `.isproj`는 대소문자 구분 없이 허용한다. 저장 이름에 확장자가 없으면 `.isproj`를 붙이고, 다른 확장자가 있으면 수정을 안내한다. `.isproj.isproj` 중복 생성을 금지한다. |
|
||||
| 내용 검사 | 확장자뿐 아니라 JSON 파싱, `schemaVersion`, 필수 필드와 지원 대상 검증을 수행한다. 일반 JSON·상위 스키마·손상 파일은 프로젝트로 취급하지 않는다. |
|
||||
|
||||
@@ -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/)
|
||||
@@ -21,6 +21,9 @@
|
||||
| [08 검증과 구현 순서](08-validation-and-roadmap.md) | 자동 검증, Windows VM 시험, 단계별 완료 기준 |
|
||||
| [09 설치 과정과 UI 편집](09-install-flow-and-ui.md) | 단계 추가·삭제·순서·조건, UI 테마, 사용자 정의 Burn 화면 |
|
||||
| [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 설정이 없는 현재 설계용 입력 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user