diff --git a/package-lock.json b/package-lock.json index f66a5e0834e..c95db0a4940 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14273,6 +14273,15 @@ "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", "dev": true }, + "node_modules/@types/write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-oMJ8hauPkPOPZf7WAvwcyR0r3By7c/MeJr+WVpt1wWiU3ZiPc+BbVXy4Y756wqH+I2p3VR+o5AM5eMKyuHfDng==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -48538,6 +48547,7 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", + "@types/write-file-atomic": "^4.0.1", "chai": "^4.3.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", @@ -48547,6 +48557,7 @@ "prettier": "^2.7.1", "sinon": "^9.2.3", "typescript": "^5.0.4", + "write-file-atomic": "^5.0.1", "zod": "^3.22.2" } }, @@ -48559,6 +48570,18 @@ "node": ">=0.3.1" } }, + "packages/compass-user-data/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/compass-user-data/node_modules/sinon": { "version": "9.2.4", "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", @@ -48577,6 +48600,19 @@ "url": "https://opencollective.com/sinon" } }, + "packages/compass-user-data/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "packages/compass-utils": { "name": "@mongodb-js/compass-utils", "version": "0.4.0", @@ -61359,6 +61395,7 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", + "@types/write-file-atomic": "^4.0.1", "chai": "^4.3.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", @@ -61368,6 +61405,7 @@ "prettier": "^2.7.1", "sinon": "^9.2.3", "typescript": "^5.0.4", + "write-file-atomic": "*", "zod": "^3.22.2" }, "dependencies": { @@ -61377,6 +61415,12 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, "sinon": { "version": "9.2.4", "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", @@ -61390,6 +61434,16 @@ "nise": "^4.0.4", "supports-color": "^7.1.0" } + }, + "write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + } } } }, @@ -69199,6 +69253,15 @@ "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", "dev": true }, + "@types/write-file-atomic": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/write-file-atomic/-/write-file-atomic-4.0.1.tgz", + "integrity": "sha512-oMJ8hauPkPOPZf7WAvwcyR0r3By7c/MeJr+WVpt1wWiU3ZiPc+BbVXy4Y756wqH+I2p3VR+o5AM5eMKyuHfDng==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", diff --git a/packages/compass-preferences-model/src/storage.spec.ts b/packages/compass-preferences-model/src/storage.spec.ts index cb181b46fa6..3c4624f7f13 100644 --- a/packages/compass-preferences-model/src/storage.spec.ts +++ b/packages/compass-preferences-model/src/storage.spec.ts @@ -136,6 +136,23 @@ describe('storage', function () { ).to.deep.equal(getDefaultPreferences()); }); + it('when invalid json is stored, it sets the defaults', async function () { + const storage = new StoragePreferences(tmpDir); + + const preferencesFile = getPreferencesFile(tmpDir); + await fs.mkdir(getPreferencesFolder(tmpDir)); + await fs.writeFile(preferencesFile, '{}}', 'utf-8'); + + // Ensure it exists + expect(async () => await fs.access(preferencesFile)).to.not.throw; + + await storage.setup(); + + expect( + JSON.parse((await fs.readFile(preferencesFile)).toString()) + ).to.deep.equal(getDefaultPreferences()); + }); + it('updates preferences', async function () { const storage = new StoragePreferences(tmpDir); await storage.setup(); diff --git a/packages/compass-preferences-model/src/storage.ts b/packages/compass-preferences-model/src/storage.ts index 5d119b9bc6d..ef638aeea0f 100644 --- a/packages/compass-preferences-model/src/storage.ts +++ b/packages/compass-preferences-model/src/storage.ts @@ -74,11 +74,15 @@ export class StoragePreferences extends BasePreferencesStorage { try { this.preferences = await this.readPreferences(); } catch (e) { - if ((e as any).code !== 'ENOENT') { - throw e; + if ( + (e as any).code === 'ENOENT' || // First time user + e instanceof SyntaxError // Invalid json + ) { + // Create the file for the first time + await this.userData.write(this.file, this.defaultPreferences); + return; } - // Create the file for the first time - await this.userData.write(this.file, this.defaultPreferences); + throw e; } } diff --git a/packages/compass-user-data/package.json b/packages/compass-user-data/package.json index 9ddc4b256f8..77ba19c2840 100644 --- a/packages/compass-user-data/package.json +++ b/packages/compass-user-data/package.json @@ -58,6 +58,7 @@ "@types/chai": "^4.2.21", "@types/mocha": "^9.0.0", "@types/sinon-chai": "^3.2.5", + "@types/write-file-atomic": "^4.0.1", "chai": "^4.3.6", "depcheck": "^1.4.1", "eslint": "^7.25.0", @@ -67,6 +68,7 @@ "prettier": "^2.7.1", "sinon": "^9.2.3", "typescript": "^5.0.4", + "write-file-atomic": "^5.0.1", "zod": "^3.22.2" } } diff --git a/packages/compass-user-data/src/user-data.ts b/packages/compass-user-data/src/user-data.ts index 18849e0b751..d2a18f218ff 100644 --- a/packages/compass-user-data/src/user-data.ts +++ b/packages/compass-user-data/src/user-data.ts @@ -4,6 +4,7 @@ import path from 'path'; import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging'; import { getStoragePath } from '@mongodb-js/compass-utils'; import type { z } from 'zod'; +import writeFile from 'write-file-atomic'; const { log, mongoLogId } = createLoggerAndTelemetry('COMPASS-USER-STORAGE'); @@ -172,7 +173,7 @@ export class UserData { const filepath = this.getFileName(id); const absolutePath = await this.getFileAbsolutePath(filepath); try { - await fs.writeFile(absolutePath, this.serialize(content), { + await writeFile(absolutePath, this.serialize(content), { encoding: 'utf-8', }); return true; diff --git a/packages/compass/src/main/index.ts b/packages/compass/src/main/index.ts index 7146b14e4e5..c60c06f3d89 100644 --- a/packages/compass/src/main/index.ts +++ b/packages/compass/src/main/index.ts @@ -108,7 +108,13 @@ async function main(): Promise { }); } - await CompassApplication.init(mode, globalPreferences); + try { + await CompassApplication.init(mode, globalPreferences); + } catch (e) { + await handleUncaughtException(e as Error); + await CompassApplication.runExitHandlers().finally(() => app.exit(1)); + return; + } if (mode === 'CLI') { let exitCode = 0;