feat: use isproj-only project files and local settings

Closes #6
This commit is contained in:
jychoi4 committed 2026-11-06 14:46:00 +09:00
1 parent 3071e0d1ef
commit 916b79e5d6
12 files changed
+230 -40

No files matched your search

+1 -1
View File
@@ -67,7 +67,7 @@
"id": "projectPath",
"type": "promptString",
"description": "프로젝트 JSON 경로 (절대 경로 또는 src 기준 상대 경로, 따옴표 없이 입력)",
"default": "fixtures/package-project.json"
"default": "fixtures/InstallerProject.isproj"
},
{
"id": "targetId",
+10 -10
View File
@@ -12,7 +12,7 @@ npm.cmd ci
npm.cmd run tauri dev
```
새 프로젝트를 만들거나 `.json` 파일을 연 뒤 앱 정보 → 파일 구성에서 소스 폴더 연결 → 패키지 설정 → 검증·빌드에서 SDK 도구 선택 → 입력 검사 → 빌드 계획 생성 → MSIX 빌드 순서로 사용한다. 포함/제외 규칙과 의존성은 프로젝트 JSON 편집에서 수정한다. 기본 프로젝트의 Publisher는 예시이며 자신의 패키지 identity에 맞게 바꿔야 한다.
새 프로젝트를 만들거나 `.isproj` 파일을 연 뒤 앱 정보 → 파일 구성에서 소스 폴더 연결 → 패키지 설정 → 검증·빌드에서 SDK 도구 선택 → 입력 검사 → 빌드 계획 생성 → MSIX 빌드 순서로 사용한다. 포함/제외 규칙과 의존성은 프로젝트 JSON 편집에서 수정한다. 기본 프로젝트의 Publisher는 예시이며 자신의 패키지 identity에 맞게 바꿔야 한다.
시작 화면은 왼쪽 **최근 프로젝트 목록**, 오른쪽 **프로젝트 열기 / 새 프로젝트 만들기** 버튼으로 구성한다. 최근 목록은 이 버전의 GUI에서 열거나 만든 프로젝트를 최대 20개 보관하며 앱을 다시 실행해도 유지된다. 이름·경로 검색과 오늘/이번 주/이번 달/이전 그룹을 제공한다. 목록의 `×`는 기록만 제거하며 프로젝트 파일을 삭제하지 않는다. 이동·삭제된 파일은 위치 확인 안내를 표시한다.
@@ -52,7 +52,7 @@ VS Code 빌드/시험 산출물은 `src/target/vscode`에 분리한다. 기존 `
| `MSIX CLI: Plan release` | 프로젝트 경로와 대상 ID를 받아 계획 생성 |
| `MSIX CLI: Build plan` | 프로젝트 경로와 새 계획 UUID를 받아 실제 MSIX 생성 |
CLI 입력 경로는 절대 경로나 `src` 기준 상대 경로를 따옴표 없이 입력한다. 기본값 `fixtures/package-project.json`은 Publisher가 미확정인 초안이므로 검증 오류를 확인하는 용도다. 실제 빌드는 소스/SDK가 연결된 프로젝트를 사용한다. 시험 앱을 생성했다면 `.work/demo/FixtureAlpha/package-project.json`을 입력할 수 있다. 계획 ID는 `Plan release` 결과의 `id`를 사용한다.
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 ...`로 명시 실행한다.
@@ -69,20 +69,20 @@ HKCU 초기화·COM/파일 연결·ISM 가져오기와 MSI 속성 치환은 아
아래 명령은 `src`에서 실행한다. stdout은 JSON, 오류는 stderr JSON이다. 개발자 서명 모드는 실제 MSIX 서명을 수행한다. 앱 설치와 PC의 인증서 신뢰 등록은 자동 수행하지 않는다.
```powershell
cargo run -p package-builder -- init --project D:\packages\demo\package-project.json
cargo run -p package-builder -- validate --project D:\packages\demo\package-project.json
cargo run -p package-builder -- plan release --project D:\packages\demo\package-project.json --target msix-direct
cargo run -p package-builder -- build --project D:\packages\demo\package-project.json --plan-id <계획의-id>
cargo run -p package-builder -- job status --project D:\packages\demo\package-project.json --id <계획의-id>
cargo run -p package-builder -- export --project D:\packages\demo\package-project.json --release <계획의-id> --output D:\exports\new-folder
cargo run -p package-builder -- init --project D:\packages\demo\InstallerProject.isproj
cargo run -p package-builder -- validate --project D:\packages\demo\InstallerProject.isproj
cargo run -p package-builder -- plan release --project D:\packages\demo\InstallerProject.isproj --target msix-direct
cargo run -p package-builder -- build --project D:\packages\demo\InstallerProject.isproj --plan-id <계획의-id>
cargo run -p package-builder -- job status --project D:\packages\demo\InstallerProject.isproj --id <계획의-id>
cargo run -p package-builder -- export --project D:\packages\demo\InstallerProject.isproj --release <계획의-id> --output D:\exports\new-folder
cargo run -p package-builder -- schema
```
프로젝트는 `.json`만 지원하며 내부 내용은 UTF-8 JSON v2다. 새 프로젝트의 기본 이름은 `package-project.json`이고, 확장자를 생략하면 `.json`를 붙인다. 기존 `.json` 프로젝트의 열기·호환·변환은 제공하지 않는다. 기존 JSON 항목은 최근 목록에서도 숨긴다.
프로젝트는 `.isproj`만 지원하며 내부 내용은 UTF-8 JSON v2다. 새 프로젝트의 기본 이름은 `InstallerProject.isproj`이고, 확장자를 생략하면 `.isproj`를 붙인다. 기존 `.json` 프로젝트의 열기·호환·변환은 제공하지 않는다. 기존 JSON 항목은 최근 목록에서도 숨긴다.
별도의 프로젝트 ID는 사용하지 않는다. MSIX 앱 계열은 패키지 설정의 Identity Name과 Publisher로 관리한다. 기존 `projectId` 필드가 포함된 개발용 파일은 자동 변환하지 않고 알 수 없는 필드로 거부한다. 현재 fixture와 샘플 생성 스크립트는 해당 필드 없이 프로젝트를 만든다.
프로젝트 옆 `<프로젝트 파일명>.local.json`에서 소스 alias와 SDK를 연결한다. 예를 들어 `package-project.local.json`이다. 상대 소스 경로는 프로젝트 폴더 기준이다. 기존 `package-project.local.json`은 읽지 않으며 local 파일은 Git에 넣지 않는다. 저장 시 이전 프로젝트는 `.json.bak`, 이전 로컬 설정은 `.json.local.json.bak`으로 보관한다.
프로젝트 옆 `<프로젝트 파일명>.local.json`에서 소스 alias와 SDK를 연결한다. 예를 들어 `InstallerProject.isproj.local.json`이다. 상대 소스 경로는 프로젝트 폴더 기준이다. 기존 `package-project.local.json`은 읽지 않으며 local 파일은 Git에 넣지 않는다. 저장 시 이전 프로젝트는 `.isproj.bak`, 이전 로컬 설정은 `.isproj.local.json.bak`으로 보관한다.
```json
{
+5 -5
View File
@@ -121,7 +121,7 @@ async fn open_project(
let Some(file) = rfd::AsyncFileDialog::new()
.set_parent(&window)
.set_title("프로젝트 열기")
.add_filter("Installer Studio 프로젝트", &["json"])
.add_filter("Installer Studio 프로젝트", &["isproj"])
.pick_file()
.await
else {
@@ -142,8 +142,8 @@ async fn new_project(
let Some(file) = rfd::AsyncFileDialog::new()
.set_parent(&window)
.set_title("새 프로젝트 만들기")
.set_file_name("package-project.json")
.add_filter("Installer Studio 프로젝트", &["json"])
.set_file_name("InstallerProject.isproj")
.add_filter("Installer Studio 프로젝트", &["isproj"])
.save_file()
.await
else {
@@ -337,7 +337,7 @@ mod tests {
#[test]
fn close_clears_the_session_without_changing_the_project() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("project.json");
let path = temp.path().join("project.isproj");
let doc = infra::create_project(&path).unwrap();
let original = std::fs::read(&path).unwrap();
let session = Session::default();
@@ -350,7 +350,7 @@ mod tests {
#[test]
fn close_is_rejected_while_build_is_running() {
let temp = tempfile::tempdir().unwrap();
let doc = infra::create_project(&temp.path().join("project.json")).unwrap();
let doc = infra::create_project(&temp.path().join("project.isproj")).unwrap();
let session = Session::default();
set_document(&session, doc).unwrap();
session.busy.store(true, Ordering::SeqCst);
+21 -8
View File
@@ -74,6 +74,7 @@ impl RecentStore {
Ok(history
.projects
.iter()
.filter(|project| package_infra::is_project_path(&project.path))
.map(|project| RecentView {
available: project.path.is_file(),
project: project.clone(),
@@ -125,7 +126,7 @@ impl RecentStore {
history
.projects
.iter()
.find(|entry| entry.id == id)
.find(|entry| entry.id == id && package_infra::is_project_path(&entry.path))
.map(|entry| entry.path.clone())
.context("최근 목록에서 프로젝트를 찾을 수 없습니다. 목록을 새로 고치세요.")
},
@@ -163,8 +164,8 @@ mod tests {
fn history_persists_deduplicates_and_removal_keeps_project_file() {
let temp = tempfile::tempdir().unwrap();
let store = RecentStore(temp.path().join("history"));
let mut first = package_infra::create_project(&temp.path().join("first.json")).unwrap();
let second = package_infra::create_project(&temp.path().join("second.json")).unwrap();
let mut first = package_infra::create_project(&temp.path().join("first.isproj")).unwrap();
let second = package_infra::create_project(&temp.path().join("second.isproj")).unwrap();
store.remember(&first, true).unwrap();
let first_id = store.list().unwrap()[0].project.id.clone();
store.remember(&second, true).unwrap();
@@ -186,14 +187,14 @@ mod tests {
fn history_is_bounded_and_missing_files_are_visible() {
let temp = tempfile::tempdir().unwrap();
let store = RecentStore(temp.path().join("history"));
let mut doc = package_infra::create_project(&temp.path().join("fixture.json")).unwrap();
let mut doc = package_infra::create_project(&temp.path().join("fixture.isproj")).unwrap();
for i in 0..25 {
doc.path = temp.path().join(format!("missing-{i}.json"));
doc.path = temp.path().join(format!("missing-{i}.isproj"));
store.remember(&doc, true).unwrap();
}
let entries = store.list().unwrap();
assert_eq!(entries.len(), MAX_RECENT);
assert!(entries[0].project.path.ends_with("missing-24.json"));
assert!(entries[0].project.path.ends_with("missing-24.isproj"));
assert!(entries.iter().all(|entry| !entry.available));
}
@@ -204,10 +205,22 @@ mod tests {
fs::create_dir_all(&store.0).unwrap();
let history_path = store.0.join("recent-projects.json");
fs::write(&history_path, b"broken").unwrap();
let doc = package_infra::create_project(&temp.path().join("fixture.json")).unwrap();
let doc = package_infra::create_project(&temp.path().join("fixture.isproj")).unwrap();
assert!(store.remember(&doc, true).is_err());
assert_eq!(fs::read(history_path).unwrap(), b"broken");
}
#[test]
fn legacy_history_is_hidden_and_cannot_be_opened_by_id() {
let temp = tempfile::tempdir().unwrap();
let store = RecentStore(temp.path().join("history"));
let mut doc = package_infra::create_project(&temp.path().join("current.isproj")).unwrap();
store.remember(&doc, true).unwrap();
doc.path = temp.path().join("legacy.json");
store.remember(&doc, true).unwrap();
let id = store.load().unwrap().projects[0].id.clone();
assert_eq!(store.list().unwrap().len(), 1);
assert!(store.find(&id).is_err());
assert_eq!(store.load().unwrap().projects.len(), 2);
}
}
+3 -3
View File
@@ -4,7 +4,7 @@ import { displayPath, groupRecentProjects, renderStartScreen } from '../src/star
import { allowProjectTransition } from '../src/project-transition.ts';
const recent = (id, year, month, day, name = id) => ({
id, path: `C:\\projects\\${id}\\package-project.json`, displayName: name,
id, path: `C:\\projects\\${id}\\InstallerProject.isproj`, displayName: name,
lastOpenedAt: new Date(year, month - 1, day, 10).getTime() / 1000, available: true,
});
@@ -18,8 +18,8 @@ test('recent projects are grouped in local calendar order and searchable by name
});
test('extended Windows paths are displayed without changing their UNC semantics', () => {
assert.equal(displayPath('\\\\?\\C:\\apps\\project.json'), 'C:\\apps\\project.json');
assert.equal(displayPath('\\\\?\\UNC\\server\\share\\project.json'), '\\\\server\\share\\project.json');
assert.equal(displayPath('\\\\?\\C:\\apps\\project.isproj'), 'C:\\apps\\project.isproj');
assert.equal(displayPath('\\\\?\\UNC\\server\\share\\project.isproj'), '\\\\server\\share\\project.isproj');
});
test('start screen has left recent projects and right actions without fabricated entries', () => {
+1 -1
View File
@@ -430,7 +430,7 @@ mod tests {
#[test]
fn example_requires_publisher_but_not_exe() {
let project: Project =
serde_json::from_str(include_str!("../../../fixtures/package-project.json"))
serde_json::from_str(include_str!("../../../fixtures/InstallerProject.isproj"))
.unwrap();
assert_eq!(
validate(&project)
+37 -5
View File
@@ -58,12 +58,39 @@ fn atomic_bytes(path: &Path, bytes: &[u8]) -> Result<()> {
temp.persist(path).map_err(|error| error.error)?;
Ok(())
}
pub fn is_project_path(path: &Path) -> bool {
path.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("isproj"))
&& !path.file_stem().is_some_and(|stem| {
Path::new(stem)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("isproj"))
})
}
fn check_project_path(path: &Path) -> Result<()> {
ensure!(is_project_path(path), "UnsupportedProjectExtension: .isproj 프로젝트만 지원합니다. 기존 .json 프로젝트는 지원하지 않습니다.");
Ok(())
}
pub fn new_project_path(path: &Path) -> Result<PathBuf> {
ensure!(path.file_name().is_some(), "MissingFilename");
let path = if path.extension().is_none() {
path.with_extension("isproj")
} else {
path.to_path_buf()
};
check_project_path(&path)?;
Ok(path)
}
pub fn appended_path(path: &Path, suffix: &str) -> PathBuf {
let mut name = path.as_os_str().to_os_string();
name.push(suffix);
PathBuf::from(name)
}
pub fn local_config_path(path: &Path) -> PathBuf {
appended_path(path, ".local.json")
}
pub fn open_project(path: &Path) -> Result<ProjectDocument> {
check_project_path(path)?;
reject_links(path)?;
let path = path.canonicalize()?;
let bytes = fs::read(&path)?;
@@ -73,7 +100,7 @@ pub fn open_project(path: &Path) -> Result<ProjectDocument> {
project.schema_version == 2,
"UnsupportedSchema: v2 프로젝트만 열 수 있습니다. 원본을 자동 변환하지 않습니다."
);
let local_path = path.parent().unwrap().join("package-project.local.json");
let local_path = local_config_path(&path);
reject_links(&local_path)?;
let local_bytes = if local_path.exists() {
fs::read(&local_path)?
@@ -112,8 +139,8 @@ pub fn save_project(document: &ProjectDocument) -> Result<ProjectDocument> {
);
// A draft may be incomplete, but never silently accept a different schema.
ensure!(document.project.schema_version == 2, "UnsupportedSchema");
let project_backup = document.path.with_extension("json.bak");
let local_path = document.path.parent().unwrap().join("package-project.local.json");
let project_backup = appended_path(&document.path, ".bak");
let local_path = local_config_path(&document.path);
let local_backup = appended_path(&local_path, ".bak");
for backup in [&project_backup, &local_backup] {
reject_links(backup)?;
@@ -141,12 +168,17 @@ pub fn save_project(document: &ProjectDocument) -> Result<ProjectDocument> {
open_project(&document.path)
}
pub fn create_project(path: &Path) -> Result<ProjectDocument> {
ensure!(path.file_name().is_some(), "MissingFilename");
let normalized = new_project_path(path)?;
let path = normalized.as_path();
reject_links(path)?;
fs::create_dir_all(path.parent().context("MissingParent")?)?;
let _lock = lock_project(path.parent().unwrap())?;
ensure!(
!local_config_path(path).exists(),
"LocalConfigAlreadyExists"
);
let mut value: serde_json::Value =
serde_json::from_str(include_str!("../../../fixtures/package-project.json"))?;
serde_json::from_str(include_str!("../../../fixtures/InstallerProject.isproj"))?;
value["formats"]["msix"]["identity"]["publisher"] =
serde_json::Value::String("CN=Example Publisher".into());
let mut file = File::options()
@@ -0,0 +1,145 @@
use package_infra as infra;
use std::fs;
#[test]
fn new_projects_normalize_extension_and_roundtrip_local_settings_and_backups() {
let temp = tempfile::tempdir().unwrap();
let mut doc = infra::create_project(&temp.path().join("한글 프로젝트")).unwrap();
assert!(doc.path.ends_with("한글 프로젝트.isproj"));
let original = fs::read(&doc.path).unwrap();
assert!(serde_json::from_slice::<serde_json::Value>(&original)
.unwrap()
.get("projectId")
.is_some());
doc.project.product.display_name = "변경된 제품".into();
doc.local
.sources
.insert("appPayload".into(), "payload".into());
doc.local.developer_certificate_thumbprint = Some("A".repeat(40));
let saved = infra::save_project(&doc).unwrap();
assert_eq!(saved.local.sources["appPayload"], "payload");
assert_eq!(
saved.local.developer_certificate_thumbprint,
doc.local.developer_certificate_thumbprint
);
assert_eq!(
saved.project.formats.msix.identity.name,
doc.project.formats.msix.identity.name
);
assert_eq!(
saved.project.formats.msix.identity.publisher,
doc.project.formats.msix.identity.publisher
);
assert_eq!(
fs::read(infra::appended_path(&doc.path, ".bak")).unwrap(),
original
);
let local_path = infra::local_config_path(&doc.path);
assert!(local_path.ends_with("한글 프로젝트.isproj.local.json"));
let local_bytes = fs::read(&local_path).unwrap();
infra::save_project(&saved).unwrap();
assert_eq!(
fs::read(infra::appended_path(&local_path, ".bak")).unwrap(),
local_bytes
);
assert!(!temp.path().join("한글 프로젝트.json.bak").exists());
}
#[test]
fn json_and_duplicate_extensions_are_rejected_even_with_valid_contents() {
let temp = tempfile::tempdir().unwrap();
let doc = infra::create_project(&temp.path().join("Sample.ISPROJ")).unwrap();
assert!(infra::open_project(&doc.path).is_ok());
for name in [
"legacy.json",
"bad.txt",
"duplicate.isproj.isproj",
"no-extension",
] {
let path = temp.path().join(name);
fs::copy(&doc.path, &path).unwrap();
assert!(infra::open_project(&path)
.unwrap_err()
.to_string()
.contains("UnsupportedProjectExtension"));
if name != "no-extension" {
assert!(infra::create_project(&path)
.unwrap_err()
.to_string()
.contains("UnsupportedProjectExtension"));
}
}
assert!(infra::create_project(&doc.path).is_err());
fs::write(temp.path().join("broken.isproj"), "not json").unwrap();
assert!(infra::open_project(&temp.path().join("broken.isproj")).is_err());
}
#[test]
fn no_legacy_local_fallback_and_no_overwrite_of_orphan_local_settings() {
let temp = tempfile::tempdir().unwrap();
fs::write(
temp.path().join("package-project.local.json"),
r#"{"sources":{"legacy":"do-not-load"},"makeAppx":null}"#,
)
.unwrap();
let doc = infra::create_project(&temp.path().join("fresh.isproj")).unwrap();
assert!(doc.local.sources.is_empty());
let path = temp.path().join("orphan.isproj");
fs::write(infra::local_config_path(&path), "keep").unwrap();
assert!(infra::create_project(&path)
.unwrap_err()
.to_string()
.contains("LocalConfigAlreadyExists"));
assert!(!path.exists());
}
#[test]
fn local_revision_conflicts_and_failed_backup_preserve_both_files() {
let temp = tempfile::tempdir().unwrap();
let doc = infra::create_project(&temp.path().join("sample.isproj")).unwrap();
let mut doc = infra::save_project(&doc).unwrap();
let local_path = infra::local_config_path(&doc.path);
let original = fs::read(&doc.path).unwrap();
let local = fs::read(&local_path).unwrap();
fs::write(&local_path, b"{\"sources\":{},\"makeAppx\":null}\n").unwrap();
assert!(infra::save_project(&doc)
.unwrap_err()
.to_string()
.contains("RevisionConflict"));
fs::write(&local_path, &local).unwrap();
fs::create_dir(infra::appended_path(&local_path, ".bak")).unwrap();
doc.project.product.display_name = "must not commit".into();
doc.local
.sources
.insert("appPayload".into(), "changed".into());
assert!(infra::save_project(&doc).is_err());
assert_eq!(fs::read(&doc.path).unwrap(), original);
assert_eq!(fs::read(&local_path).unwrap(), local);
}
#[cfg(windows)]
#[test]
fn failed_project_replacement_restores_previous_local_settings() {
use std::os::windows::fs::OpenOptionsExt;
let temp = tempfile::tempdir().unwrap();
let doc = infra::create_project(&temp.path().join("locked.isproj")).unwrap();
let mut doc = infra::save_project(&doc).unwrap();
let local_path = infra::local_config_path(&doc.path);
let project_before = fs::read(&doc.path).unwrap();
let local_before = fs::read(&local_path).unwrap();
// Permit reading and writing, but hold back FILE_SHARE_DELETE so replacement fails.
let _held = fs::OpenOptions::new()
.read(true)
.share_mode(3)
.open(&doc.path)
.unwrap();
doc.project.product.display_name = "must roll back".into();
doc.local
.sources
.insert("appPayload".into(), "changed".into());
assert!(infra::save_project(&doc).is_err());
assert_eq!(fs::read(&doc.path).unwrap(), project_before);
assert_eq!(fs::read(local_path).unwrap(), local_before);
}
+1 -1
View File
@@ -18,7 +18,7 @@ fn png(path: &Path, size: u32) {
}
fn fixture() -> (tempfile::TempDir, PathBuf) {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("package-project.json");
let path = temp.path().join("InstallerProject.isproj");
let mut doc = infra::create_project(&path).unwrap();
let payload = temp.path().join("payload");
fs::create_dir_all(payload.join("Assets")).unwrap();
File renamed without changes.
+4 -4
View File
@@ -22,13 +22,13 @@ foreach ($name in @('FixtureAlpha', 'FixtureBeta')) {
} finally { $env:FIXTURE_NAME = $previousName }
Copy-Item -LiteralPath (Join-Path $sourceRoot 'apps/desktop/src-tauri/icons/Square44x44Logo.png') -Destination (Join-Path $assetRoot 'Logo44.png')
Copy-Item -LiteralPath (Join-Path $sourceRoot 'apps/desktop/src-tauri/icons/Square150x150Logo.png') -Destination (Join-Path $assetRoot 'Logo150.png')
$project = Get-Content -Raw -Encoding UTF8 (Join-Path $sourceRoot 'fixtures/package-project.json') | ConvertFrom-Json
$project = Get-Content -Raw -Encoding UTF8 (Join-Path $sourceRoot 'fixtures/InstallerProject.isproj') | ConvertFrom-Json
$project.projectId = $name
$project.product.displayName = $name
$project.formats.msix.identity.name = "ExamplePublisher.$name"
$project.formats.msix.identity.publisher = 'CN=Example Publisher'
[IO.File]::WriteAllText((Join-Path $projectRoot 'package-project.json'), ($project | ConvertTo-Json -Depth 30), $utf8)
[IO.File]::WriteAllText((Join-Path $projectRoot 'InstallerProject.isproj'), ($project | ConvertTo-Json -Depth 30), $utf8)
$local = @{ sources = @{ appPayload = 'payload' }; makeAppx = $sdkTool }
[IO.File]::WriteAllText((Join-Path $projectRoot 'package-project.local.json'), ($local | ConvertTo-Json -Depth 10), $utf8)
Write-Output (Join-Path $projectRoot 'package-project.json')
[IO.File]::WriteAllText((Join-Path $projectRoot 'InstallerProject.isproj.local.json'), ($local | ConvertTo-Json -Depth 10), $utf8)
Write-Output (Join-Path $projectRoot 'InstallerProject.isproj')
}
+2 -2
View File
@@ -179,8 +179,8 @@ def main():
"capabilities": ["runFullTrust"], "extensions": [], "dependencies": []}},
"targets": [{"id": "msix-direct", "format": "msix", "delivery": "package", "distribution": "direct",
"signingProfileRef": None}]}
write_json(out / "package-project.json", project)
write_json(out / "package-project.local.json", {"sources": local_sources, "makeAppx": str(args.makeappx.resolve())})
write_json(out / "InstallerProject.isproj", project)
write_json(out / "InstallerProject.isproj.local.json", {"sources": local_sources, "makeAppx": str(args.makeappx.resolve())})
write_json(out / "reports/file-mapping.json", mappings)
with (out / "reports/file-mapping.csv").open("w", encoding="utf-8-sig", newline="") as f:
writer = csv.DictWriter(f, fieldnames=list(mappings[0])); writer.writeheader(); writer.writerows(mappings)