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:
jychoi4andClaude Opus 5.5 committed 2026-11-11 11:10:00 +09:00
1 parent 905e6d4eaa
commit 366c8fe173
69 files changed
+9295 -441

No files matched your search

+12 -2
View File
@@ -19,7 +19,7 @@ fn png(path: &Path, size: u32) {
fn fixture() -> (tempfile::TempDir, PathBuf) {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("InstallerProject.isproj");
let mut doc = infra::create_project(&path).unwrap();
let mut doc = infra::create_project(&path, package_domain::ProjectKind::Msix).unwrap();
let payload = temp.path().join("payload");
fs::create_dir_all(payload.join("Assets")).unwrap();
let mut pe = vec![0; 256];
@@ -233,6 +233,15 @@ fn schema_file_matches_rust_contract() {
assert_eq!(actual, expected);
}
#[test]
fn version_schema_file_matches_rust_contract() {
// `package-builder version-schema` emits this; it describes the EXE *.isver meta files.
let actual = serde_json::to_value(schemars::schema_for!(package_domain::ExeVersion)).unwrap();
let expected: serde_json::Value =
serde_json::from_str(include_str!("../../../schemas/exe-version.schema.json")).unwrap();
assert_eq!(actual, expected);
}
#[test]
#[ignore = "Requires a real Windows SDK: set MSIX_TEST_MAKEAPPX and run --ignored"]
fn real_sdk_packages_two_products_and_rejects_stale_plans() {
@@ -240,7 +249,8 @@ fn real_sdk_packages_two_products_and_rejects_stale_plans() {
for name in ["FixtureAlpha", "FixtureBeta"] {
let (temp, path) = fixture();
let mut doc = infra::open_project(&path).unwrap();
doc.project.formats.msix.identity.name = format!("ExamplePublisher.{name}");
doc.project.formats.msix.as_mut().unwrap().identity.name =
format!("ExamplePublisher.{name}");
doc.project.product.display_name = name.into();
doc.local.make_appx = Some(tool.clone());
doc.project.registry = serde_json::from_value(serde_json::json!([