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,