feat: generate and verify MSIX registry hives

Closes #3
This commit is contained in:
jychoi4 committed 2026-11-03 16:34:00 +09:00
1 parent 6213fec7d2
commit fdbc9a9d1b
7 files changed
+598 -2

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
export type RegistryEntry = {
id: string; hive: string; key: string; name: string; view: string; valueType: string; data: string;
};
export const registryTypes = ['REG_SZ', 'REG_EXPAND_SZ', 'REG_DWORD', 'REG_QWORD', 'REG_MULTI_SZ', 'REG_BINARY'];
const esc = (value: string) => value.replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]!);
export function newRegistryEntry(architecture: string, id = `registry-${crypto.randomUUID()}`): RegistryEntry {
return { id, hive: 'HKLM', key: 'Software\\Baron\\Application', name: '', view: architecture === 'x86' ? '32' : '64', valueType: 'REG_SZ', data: '' };
}
export function renderRegistryEditor(entries: RegistryEntry[], disabled: boolean): string {
const lock = disabled ? 'disabled' : '';
return `<div class="callout"><strong>MSIX 앱 전용 레지스트리</strong><br>HKLM\\Software 아래의 설정을 패키지에 포함합니다. 빌드 PC의 레지스트리는 변경하지 않습니다. HKCU 초기화와 시스템 전체 COM·파일 연결 등록은 아직 지원하지 않습니다.</div>
<div class="registry-toolbar"><strong>${entries.length}개 설정</strong><button class="primary" data-action="registry-add" ${lock}>레지스트리 값 추가</button></div>
${entries.length ? '' : '<p class="muted">등록된 값이 없습니다. 필요한 키, 값 이름, 형식과 데이터를 추가하세요.</p>'}
${entries.map((entry, index) => {
const path = `registry.${index}`;
const input = (label: string, key: keyof RegistryEntry, help = '') => `<label class="field">${label}<input data-field="${path}.${key}" value="${esc(entry[key])}" ${lock}><small>${help}</small></label>`;
const select = (label: string, key: keyof RegistryEntry, choices: string[]) => `<label class="field">${label}<select data-field="${path}.${key}" ${lock}>${[...new Set([entry[key], ...choices])].map(value => `<option value="${esc(value)}" ${value === entry[key] ? 'selected' : ''}>${esc(value)}</option>`).join('')}</select></label>`;
return `<section class="registry-card" aria-label="레지스트리 항목 ${index + 1}"><div class="registry-toolbar"><strong>항목 ${index + 1}</strong><button data-action="registry-remove:${index}" ${lock} aria-label="레지스트리 항목 ${index + 1} 삭제">삭제</button></div><div class="form-grid">
${input('항목 ID', 'id', '프로젝트 안에서 고유한 ID')}${select('루트', 'hive', ['HKLM'])}
${input('키 경로', 'key', '예: Software\\Baron\\EG-BIM')}${select('레지스트리 뷰', 'view', ['64', '32'])}
${input('값 이름', 'name', '비워 두면 해당 키의 기본값')}${select('값 형식', 'valueType', registryTypes)}
</div><label class="field">데이터<textarea data-field="${path}.data" rows="3" spellcheck="false" ${lock}>${esc(entry.data)}</textarea><small>문자열은 그대로 저장합니다. 숫자는 10진수 또는 0x16진수, MULTI_SZ는 한 줄에 한 값, BINARY는 00 FF처럼 입력하세요. QWORD도 숫자 손실 없이 저장합니다. [INSTALLDIR] 같은 MSI 속성은 치환하지 않습니다.</small></label></section>`;
}).join('')}`;
}
@@ -0,0 +1,24 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { newRegistryEntry, renderRegistryEditor } from '../src/registry-editor.ts';
test('new entries select the app view and preserve lossless text data', () => {
assert.equal(newRegistryEntry('x86', 'one').view, '32');
const entry = newRegistryEntry('x64', 'two');
entry.valueType = 'REG_QWORD'; entry.data = '18446744073709551615';
assert.match(renderRegistryEditor([entry], false), /18446744073709551615/);
assert.equal(JSON.parse(JSON.stringify(entry)).data, entry.data);
});
test('registry editor escapes markup and addresses rows after deletion', () => {
const first = newRegistryEntry('x64', 'first');
const second = newRegistryEntry('x64', 'second');
second.data = '</textarea><script>alert(1)</script>';
second.name = '" onfocus="bad';
const entries = [first, second]; entries.splice(0, 1);
const html = renderRegistryEditor(entries, true);
assert.ok(!html.includes('<script>'));
assert.ok(html.includes('&lt;/textarea&gt;'));
assert.ok(html.includes('&quot; onfocus=&quot;bad'));
assert.ok(html.includes('registry.0.data'));
assert.ok(html.includes('data-action="registry-remove:0" disabled'));
});