fix(config): refuse partial JSONC parses that wipe opencode.jsonc
jsonc-parser was returning truncated trees (often only `$schema`) when configs contained JSON5-style unquoted keys. Config mutations then backed up and overwrote the full file with that stub. Check parse errors on read and refuse to overwrite unparseable files on write in web and VS Code. Fixes #2923 Co-authored-by: serkraser <serkraser@gmail.com>
This commit is contained in:
co-authored by
serkraser
parent
6b1e677aaf
commit
4cc090130c
@@ -0,0 +1,96 @@
|
||||
import { afterEach, beforeEach, describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { updateMcpConfig } from './opencodeConfig';
|
||||
|
||||
const PARTIAL_PARSE_CONFIG = [
|
||||
'{',
|
||||
' "$schema": "https://opencode.ai/config.json",',
|
||||
' plugin: ["opencode-see-image"],',
|
||||
' mcp: {',
|
||||
' openproject: {',
|
||||
' type: "remote",',
|
||||
' url: "https://openproject.example.com/mcp",',
|
||||
' enabled: true',
|
||||
' }',
|
||||
' },',
|
||||
' provider: {',
|
||||
' "ollama-cloud": {',
|
||||
' npm: "@ai-sdk/openai-compatible",',
|
||||
' name: "Ollama Cloud"',
|
||||
' }',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
const VALID_CONFIG = [
|
||||
'{',
|
||||
' "$schema": "https://opencode.ai/config.json",',
|
||||
' "plugin": ["opencode-see-image"],',
|
||||
' "mcp": {',
|
||||
' "openproject": {',
|
||||
' "type": "remote",',
|
||||
' "url": "https://openproject.example.com/mcp",',
|
||||
' "enabled": true',
|
||||
' }',
|
||||
' },',
|
||||
' "provider": {',
|
||||
' "ollama-cloud": {',
|
||||
' "npm": "@ai-sdk/openai-compatible",',
|
||||
' "name": "Ollama Cloud"',
|
||||
' }',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
describe('opencodeConfig JSONC parse safety (issue #2923)', () => {
|
||||
let tempDir: string;
|
||||
let previousOpenCodeConfig: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-vscode-config-parse-'));
|
||||
previousOpenCodeConfig = process.env.OPENCODE_CONFIG;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (previousOpenCodeConfig === undefined) delete process.env.OPENCODE_CONFIG;
|
||||
else process.env.OPENCODE_CONFIG = previousOpenCodeConfig;
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('refuses MCP updates that would overwrite a partial-parse config', () => {
|
||||
const configPath = path.join(tempDir, 'opencode.jsonc');
|
||||
fs.writeFileSync(configPath, PARTIAL_PARSE_CONFIG, 'utf8');
|
||||
process.env.OPENCODE_CONFIG = configPath;
|
||||
|
||||
assert.throws(
|
||||
() => updateMcpConfig('openproject', { enabled: true }),
|
||||
(error: unknown) => (
|
||||
error instanceof Error
|
||||
&& /cannot be loaded safely/.test(error.message)
|
||||
&& (error as Error & { code?: string }).code === 'INVALID_JSONC'
|
||||
),
|
||||
);
|
||||
assert.equal(fs.readFileSync(configPath, 'utf8'), PARTIAL_PARSE_CONFIG);
|
||||
assert.equal(fs.existsSync(`${configPath}.openchamber.backup`), false);
|
||||
});
|
||||
|
||||
test('preserves unrelated keys when updating a valid MCP config', () => {
|
||||
const configPath = path.join(tempDir, 'opencode.jsonc');
|
||||
fs.writeFileSync(configPath, VALID_CONFIG, 'utf8');
|
||||
process.env.OPENCODE_CONFIG = configPath;
|
||||
|
||||
updateMcpConfig('openproject', { enabled: false });
|
||||
|
||||
const rewritten = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
assert.deepEqual(rewritten.plugin, ['opencode-see-image']);
|
||||
assert.equal(rewritten.provider['ollama-cloud'].name, 'Ollama Cloud');
|
||||
assert.equal(rewritten.mcp.openproject.enabled, false);
|
||||
assert.equal(fs.readFileSync(`${configPath}.openchamber.backup`, 'utf8'), VALID_CONFIG);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user