fix(install): accept UTF-8 BOM in install configs

This commit is contained in:
Yann Roberto
2026-09-22 21:04:57 +08:00
parent bf70150eb2
commit 94ca2de35e
3 changed files with 83 additions and 1 deletions
+3 -1
View File
@@ -10,7 +10,9 @@ let cachedValidator = null;
function readJson(filePath, label) {
try {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
// Windows PowerShell 5.1 writes a BOM with Set-Content -Encoding UTF8.
const content = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
return JSON.parse(content);
} catch (error) {
throw new Error(`Invalid JSON in ${label}: ${error.message}`);
}
+57
View File
@@ -106,6 +106,63 @@ function runTests() {
}
})) passed++; else failed++;
if (test('loads a UTF-8 BOM config without changing its values or source bytes', () => {
const cwd = createTempDir('install-config-');
try {
const configPath = path.join(cwd, 'ecc-install.json');
const value = { version: 1, target: 'cursor', modules: ['rules-core'] };
writeJson(configPath, value);
const expected = loadInstallConfig(configPath);
const content = `\uFEFF${JSON.stringify(value, null, 2).replace(/\n/g, '\r\n')}\r\n`;
fs.writeFileSync(configPath, content, 'utf8');
assert.deepStrictEqual(loadInstallConfig(configPath), expected);
assert.strictEqual(fs.readFileSync(configPath, 'utf8'), content);
} finally {
cleanup(cwd);
}
})) passed++; else failed++;
if (test('preserves BOM characters inside JSON string values', () => {
const cwd = createTempDir('install-config-');
try {
const configPath = path.join(cwd, 'ecc-install.json');
const options = { note: 'custom\uFEFFvalue' };
fs.writeFileSync(configPath, `\uFEFF${JSON.stringify({ version: 1, options })}`, 'utf8');
assert.deepStrictEqual(loadInstallConfig(configPath).options, options);
} finally {
cleanup(cwd);
}
})) passed++; else failed++;
if (test('still rejects malformed JSON and misplaced BOM characters', () => {
const cwd = createTempDir('install-config-');
try {
const configPath = path.join(cwd, 'ecc-install.json');
for (const content of ['\uFEFF{', ' \uFEFF{"version":1}', '\uFEFF\uFEFF{"version":1}']) {
fs.writeFileSync(configPath, content, 'utf8');
assert.throws(() => loadInstallConfig(configPath), /Invalid JSON in ecc-install.json/);
}
} finally {
cleanup(cwd);
}
})) passed++; else failed++;
if (test('validates the schema after reading a UTF-8 BOM config', () => {
const cwd = createTempDir('install-config-');
try {
const configPath = path.join(cwd, 'ecc-install.json');
fs.writeFileSync(configPath, '\uFEFF{"version":2,"target":"ghost-target"}', 'utf8');
assert.throws(() => loadInstallConfig(configPath), /Invalid install config/);
} finally {
cleanup(cwd);
}
})) passed++; else failed++;
if (test('rejects invalid config schema values', () => {
const cwd = createTempDir('install-config-');
+23
View File
@@ -1487,6 +1487,29 @@ function runTests() {
}
})) passed++; else failed++;
for (const explicitConfig of [true, false]) {
if (test(`installs from a UTF-8 BOM config (${explicitConfig ? '--config' : 'auto-detected'})`, () => {
const homeDir = createTempDir('install-apply-bom-home-');
const projectDir = createTempDir('install-apply-bom-project-');
const configPath = path.join(projectDir, 'ecc-install.json');
const content = '\uFEFF{\r\n "version": 1,\r\n "target": "cursor",\r\n "modules": ["rules-core"]\r\n}\r\n';
try {
fs.writeFileSync(configPath, content, 'utf8');
const args = explicitConfig ? ['--config', configPath] : [];
const result = run(args, { cwd: projectDir, homeDir });
assert.strictEqual(result.code, 0, result.stderr);
assert.ok(fs.existsSync(path.join(projectDir, '.cursor', 'rules', 'common-coding-style.mdc')));
const state = readJson(path.join(projectDir, '.cursor', 'ecc-install-state.json'));
assert.deepStrictEqual(state.request.modules, ['rules-core']);
assert.strictEqual(fs.readFileSync(configPath, 'utf8'), content);
} finally {
cleanup(homeDir);
cleanup(projectDir);
}
})) passed++; else failed++;
}
if (test('preserves legacy language installs when a project config is present', () => {
const homeDir = createTempDir('install-apply-home-');
const projectDir = createTempDir('install-apply-project-');