diff --git a/src/datastore/DataStore.ts b/src/datastore/DataStore.ts index 96efef8..33ea247 100644 --- a/src/datastore/DataStore.ts +++ b/src/datastore/DataStore.ts @@ -14,6 +14,7 @@ import CaptainConstants from '../utils/CaptainConstants' import CaptainEncryptor from '../utils/Encryptor' import Utils from '../utils/Utils' import AppsDataStore from './AppsDataStore' +import validateConfigFile from './validateConfigFile' import ProDataStore from './ProDataStore' import ProjectsDataStore from './ProjectsDataStore' import RegistriesDataStore from './RegistriesDataStore' @@ -69,11 +70,14 @@ class DataStore { private projectsDataStore: ProjectsDataStore constructor(namespace: string) { + const configPath = `${CaptainConstants.captainDataDirectory}/config-${namespace}.json` + validateConfigFile(configPath) + const data = new Configstore( `captain-store-${namespace}`, // This value seems to be unused {}, { - configPath: `${CaptainConstants.captainDataDirectory}/config-${namespace}.json`, + configPath, } ) diff --git a/src/datastore/validateConfigFile.ts b/src/datastore/validateConfigFile.ts new file mode 100644 index 0000000..0a2015d --- /dev/null +++ b/src/datastore/validateConfigFile.ts @@ -0,0 +1,15 @@ +import fs = require('fs-extra') + +export default function validateConfigFile(configPath: string): void { + if (!fs.pathExistsSync(configPath)) { + return + } + + try { + fs.readJsonSync(configPath) + } catch { + throw new Error( + `Cannot load CapRover configuration from ${configPath}: the file contains malformed JSON. Fix the file or restore it from a backup before restarting CapRover.` + ) + } +} diff --git a/tests/validateConfigFile.test.ts b/tests/validateConfigFile.test.ts new file mode 100644 index 0000000..b9adbba --- /dev/null +++ b/tests/validateConfigFile.test.ts @@ -0,0 +1,40 @@ +import fs = require('fs-extra') +import os = require('os') +import path = require('path') +import validateConfigFile from '../src/datastore/validateConfigFile' + +describe('validateConfigFile', () => { + let temporaryDirectory: string + let configPath: string + + beforeEach(() => { + temporaryDirectory = fs.mkdtempSync( + path.join(os.tmpdir(), 'caprover-config-') + ) + configPath = path.join(temporaryDirectory, 'config-captain.json') + }) + + afterEach(() => { + fs.removeSync(temporaryDirectory) + }) + + test('rejects malformed JSON without modifying the file', () => { + const malformedConfig = '{"namespace":"captain",}' + fs.writeFileSync(configPath, malformedConfig) + + expect(() => validateConfigFile(configPath)).toThrow( + 'the file contains malformed JSON' + ) + expect(fs.readFileSync(configPath, 'utf8')).toBe(malformedConfig) + }) + + test('accepts valid JSON', () => { + fs.writeJsonSync(configPath, { namespace: 'captain' }) + + expect(() => validateConfigFile(configPath)).not.toThrow() + }) + + test('accepts a missing config file for first-time setup', () => { + expect(() => validateConfigFile(configPath)).not.toThrow() + }) +})