From 8cd38f5ba43f9904cee9c55accc141f338bf5934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A4=80=EC=98=81?= Date: Mon, 9 Nov 2026 13:16:00 +0900 Subject: [PATCH] refactor: remove the unused project ID Closes #7 --- src/apps/desktop/src/main.ts | 6 +++--- src/crates/package-domain/src/lib.rs | 2 -- src/crates/package-infra/tests/project_format.rs | 15 +++++++++++++-- src/fixtures/InstallerProject.isproj | 1 - src/schemas/package-project.schema.json | 4 ---- src/scripts/New-Fixtures.ps1 | 1 - src/scripts/Prepare-EgbimMsixSample.py | 2 +- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/apps/desktop/src/main.ts b/src/apps/desktop/src/main.ts index 73ea92e..cd03556 100644 --- a/src/apps/desktop/src/main.ts +++ b/src/apps/desktop/src/main.ts @@ -8,7 +8,7 @@ import { renderSigningEditor, signingStatus, type DeveloperCertificate, type Sig import { setupWindowChrome } from './window-chrome'; type Project = { - schemaVersion: number; projectId: string; + schemaVersion: number; product: { displayName: string; manufacturer: string; displayVersion: string }; sources: { alias: string }[]; files: { id: string; source: string; include: string[]; exclude: string[]; targetRelativePath: string; required: boolean }[]; @@ -174,7 +174,7 @@ function content() { if (!doc) return ''; const p = doc.project, m = p.formats.msix, a = m.applications[0]; if (tab === 6) return renderRegistryEditor(p.registry ?? [], busy || jsonDraft !== null); - if (tab === 0) return `
${field('앱 표시명', 'product.displayName', p.product.displayName)}${field('게시자 표시명', 'product.manufacturer', p.product.manufacturer)}${field('프로젝트 ID', 'projectId', p.projectId)}${field('제품 표시 버전', 'product.displayVersion', p.product.displayVersion, '패키지의 4부분 버전과 별도로 관리합니다.')}
프로젝트에는 제품 설정을, 로컬 설정에는 이 PC의 소스·SDK 경로를 저장합니다.
`; + if (tab === 0) return `
${field('앱 표시명', 'product.displayName', p.product.displayName)}${field('게시자 표시명', 'product.manufacturer', p.product.manufacturer)}${field('제품 표시 버전', 'product.displayVersion', p.product.displayVersion, '패키지의 4부분 버전과 별도로 관리합니다.')}
프로젝트에는 제품 설정을, 로컬 설정에는 이 PC의 소스·SDK 경로를 저장합니다.
`; if (tab === 1) return `

소스 폴더

${p.sources.map(s => `
${esc(s.alias)}${esc(doc!.local.sources[s.alias] ?? '폴더가 연결되지 않았습니다.')}
`).join('')}

실행 항목과 자산

${a ? `
${field('실행 파일', 'formats.msix.applications.0.executable', a.executable, '소스 경로가 아닌 패키지 내부 경로입니다.')}${field('앱 ID', 'formats.msix.applications.0.id', a.id)}${field('작은 로고 · 44 × 44 PNG', 'formats.msix.applications.0.square44x44Logo', a.square44x44Logo)}${field('타일 로고 · 150 × 150 PNG', 'formats.msix.applications.0.square150x150Logo', a.square150x150Logo)}
` : '

실행 항목이 없습니다. 프로젝트 JSON에서 추가하세요.

'}

포함 규칙

${p.files.map(r => `
${esc(r.id)}${esc(r.source)} → ${esc(r.targetRelativePath || '/')}포함 ${esc(r.include.join(', '))} · 제외 ${esc(r.exclude.join(', ') || '없음')}
`).join('')}

규칙·소스 alias·의존성은 아래 프로젝트 JSON에서 편집할 수 있습니다.

`; @@ -227,7 +227,7 @@ function render() { })); root.querySelectorAll('[data-location]').forEach(el => el.addEventListener('click', () => { const path = el.dataset.location!; - tab = path.startsWith('signing') ? 3 : path.startsWith('registry') ? 6 : path.startsWith('product') || path === 'projectId' ? 0 : path.startsWith('files') || path.startsWith('sources') || path.includes('applications') ? 1 : 2; + tab = path.startsWith('signing') ? 3 : path.startsWith('registry') ? 6 : path.startsWith('product') ? 0 : path.startsWith('files') || path.startsWith('sources') || path.includes('applications') ? 1 : 2; render(); root.querySelector(`[data-field="${CSS.escape(path)}"]`)?.focus(); })); root.querySelector('#file-filter')?.addEventListener('change', e => { fileFilter = (e.target as HTMLInputElement).value; render(); }); diff --git a/src/crates/package-domain/src/lib.rs b/src/crates/package-domain/src/lib.rs index bc98244..0a8090e 100644 --- a/src/crates/package-domain/src/lib.rs +++ b/src/crates/package-domain/src/lib.rs @@ -15,7 +15,6 @@ macro_rules! model { #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Project { pub schema_version: u32, - pub project_id: String, pub product: Product, pub sources: Vec, pub files: Vec, @@ -143,7 +142,6 @@ pub fn validate(project: &Project) -> Vec { "schemaVersion", "v2 MSIX 프로젝트만 지원합니다. 자동 변환하지 않습니다.", ); - check(identifier(&project.project_id), "InvalidId", "projectId", "Project ID must use letters, digits, dots or hyphens."); check( !project.product.display_name.trim().is_empty(), "Required", diff --git a/src/crates/package-infra/tests/project_format.rs b/src/crates/package-infra/tests/project_format.rs index 08f61ec..d830f74 100644 --- a/src/crates/package-infra/tests/project_format.rs +++ b/src/crates/package-infra/tests/project_format.rs @@ -10,7 +10,7 @@ fn new_projects_normalize_extension_and_roundtrip_local_settings_and_backups() { assert!(serde_json::from_slice::(&original) .unwrap() .get("projectId") - .is_some()); + .is_none()); doc.project.product.display_name = "변경된 제품".into(); doc.local .sources @@ -93,7 +93,18 @@ fn no_legacy_local_fallback_and_no_overwrite_of_orphan_local_settings() { assert!(!path.exists()); } - +#[test] +fn removed_project_id_is_rejected_without_modifying_the_file() { + let temp = tempfile::tempdir().unwrap(); + let doc = infra::create_project(&temp.path().join("old-field.isproj")).unwrap(); + let mut value = serde_json::to_value(&doc.project).unwrap(); + value["projectId"] = "legacy-id".into(); + let bytes = serde_json::to_vec_pretty(&value).unwrap(); + fs::write(&doc.path, &bytes).unwrap(); + let error = infra::open_project(&doc.path).unwrap_err(); + assert!(format!("{error:#}").contains("unknown field `projectId`")); + assert_eq!(fs::read(&doc.path).unwrap(), bytes); +} #[test] fn local_revision_conflicts_and_failed_backup_preserve_both_files() { diff --git a/src/fixtures/InstallerProject.isproj b/src/fixtures/InstallerProject.isproj index f23fd1c..46e1252 100644 --- a/src/fixtures/InstallerProject.isproj +++ b/src/fixtures/InstallerProject.isproj @@ -1,6 +1,5 @@ { "schemaVersion": 2, - "projectId": "sample-msix-app", "product": { "displayName": "Sample Desktop Tool", "manufacturer": "Example Publisher", diff --git a/src/schemas/package-project.schema.json b/src/schemas/package-project.schema.json index f16257a..883a5bb 100644 --- a/src/schemas/package-project.schema.json +++ b/src/schemas/package-project.schema.json @@ -44,15 +44,11 @@ "items": { "$ref": "#/$defs/Target" } - }, - "projectId": { - "type": "string" } }, "additionalProperties": false, "required": [ "schemaVersion", - "projectId", "product", "sources", "files", diff --git a/src/scripts/New-Fixtures.ps1 b/src/scripts/New-Fixtures.ps1 index 06def48..7cee5e5 100644 --- a/src/scripts/New-Fixtures.ps1 +++ b/src/scripts/New-Fixtures.ps1 @@ -23,7 +23,6 @@ foreach ($name in @('FixtureAlpha', 'FixtureBeta')) { 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/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' diff --git a/src/scripts/Prepare-EgbimMsixSample.py b/src/scripts/Prepare-EgbimMsixSample.py index 75b1236..39433a2 100644 --- a/src/scripts/Prepare-EgbimMsixSample.py +++ b/src/scripts/Prepare-EgbimMsixSample.py @@ -165,7 +165,7 @@ def main(): rules.append({"id": "sample-assets", "source": "sample-assets", "include": ["Logo44.png", "Logo150.png"], "exclude": [], "targetRelativePath": "Assets", "required": True}) project = { - "schemaVersion": 2, "projectId": "egbim-ism-msix-sample", + "schemaVersion": 2, "product": {"displayName": "EG-BIM MSIX Sample", "manufacturer": props["Manufacturer"], "displayVersion": props["ProductVersion"]}, "sources": sources, "files": rules,