aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-04-26 16:55:57 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-05-16 00:55:02 +0200
commitb971de0717a66ae6085670fe5d3a469e236a9446 (patch)
treec64352908648512bad713fc1e91dd5f07a66968c
parentrefactor: remove json5 dependency (diff)
downloadsophie-b971de0717a66ae6085670fe5d3a469e236a9446.tar.gz
sophie-b971de0717a66ae6085670fe5d3a469e236a9446.tar.zst
sophie-b971de0717a66ae6085670fe5d3a469e236a9446.zip
refactor: config file saving and debugging
Reduce the number of dependencies and the amount of code running in a security sensitive context. Instead of a deep comparison, we just compare the serialized versions of the config files. Signed-off-by: Kristóf Marussy <kristof@marussy.com>
-rw-r--r--packages/main/esbuild.config.js6
-rw-r--r--packages/main/package.json6
-rw-r--r--packages/main/src/index.ts15
-rw-r--r--packages/main/src/infrastructure/config/ConfigRepository.ts5
-rw-r--r--packages/main/src/infrastructure/config/impl/ConfigFile.ts12
-rw-r--r--packages/main/src/infrastructure/electron/impl/devTools.ts43
-rw-r--r--packages/main/src/infrastructure/electron/impl/hardenSession.ts14
-rw-r--r--packages/main/src/reactions/__tests__/synchronizeConfig.spec.ts56
-rw-r--r--packages/main/src/reactions/synchronizeConfig.ts40
-rw-r--r--yarn.lock216
10 files changed, 123 insertions, 290 deletions
diff --git a/packages/main/esbuild.config.js b/packages/main/esbuild.config.js
index ae8565d..dddda75 100644
--- a/packages/main/esbuild.config.js
+++ b/packages/main/esbuild.config.js
@@ -13,7 +13,11 @@ const thisDir = fileUrlToDirname(import.meta.url);
13const externalPackages = ['electron']; 13const externalPackages = ['electron'];
14 14
15if (process.env.MODE !== 'development') { 15if (process.env.MODE !== 'development') {
16 externalPackages.push('electron-devtools-installer', 'source-map-support'); 16 externalPackages.push(
17 'electron-devtools-installer',
18 'mkdirp',
19 'source-map-support',
20 );
17} 21}
18 22
19const gitInfo = getRepoInfo(); 23const gitInfo = getRepoInfo();
diff --git a/packages/main/package.json b/packages/main/package.json
index fceb392..fc972ba 100644
--- a/packages/main/package.json
+++ b/packages/main/package.json
@@ -11,9 +11,7 @@
11 "@sophie/service-shared": "workspace:*", 11 "@sophie/service-shared": "workspace:*",
12 "@sophie/shared": "workspace:*", 12 "@sophie/shared": "workspace:*",
13 "chalk": "^5.0.1", 13 "chalk": "^5.0.1",
14 "deep-equal": "^2.0.5",
15 "electron": "^19.0.0-alpha.1", 14 "electron": "^19.0.0-alpha.1",
16 "fs-extra": "^10.1.0",
17 "i18next": "^21.6.16", 15 "i18next": "^21.6.16",
18 "lodash-es": "^4.17.21", 16 "lodash-es": "^4.17.21",
19 "loglevel": "^1.8.0", 17 "loglevel": "^1.8.0",
@@ -21,15 +19,14 @@
21 "mobx": "^6.5.0", 19 "mobx": "^6.5.0",
22 "mobx-state-tree": "^5.1.3", 20 "mobx-state-tree": "^5.1.3",
23 "nanoid": "^3.3.3", 21 "nanoid": "^3.3.3",
24 "os-name": "^5.0.1",
25 "slug": "^5.3.0" 22 "slug": "^5.3.0"
26 }, 23 },
27 "devDependencies": { 24 "devDependencies": {
28 "@jest/globals": "^27.5.1", 25 "@jest/globals": "^27.5.1",
29 "@sophie/test-utils": "workspace:*", 26 "@sophie/test-utils": "workspace:*",
30 "@types/deep-equal": "^1.0.1",
31 "@types/electron-devtools-installer": "^2.2.2", 27 "@types/electron-devtools-installer": "^2.2.2",
32 "@types/lodash-es": "^4.17.6", 28 "@types/lodash-es": "^4.17.6",
29 "@types/mkdirp": "^1",
33 "@types/node": "^17.0.25", 30 "@types/node": "^17.0.25",
34 "@types/slug": "^5.0.3", 31 "@types/slug": "^5.0.3",
35 "@types/source-map-support": "^0.5.4", 32 "@types/source-map-support": "^0.5.4",
@@ -38,6 +35,7 @@
38 "git-repo-info": "^2.1.1", 35 "git-repo-info": "^2.1.1",
39 "jest": "^27.5.1", 36 "jest": "^27.5.1",
40 "jest-mock": "^27.5.1", 37 "jest-mock": "^27.5.1",
38 "mkdirp": "^1.0.4",
41 "source-map-support": "^0.5.21" 39 "source-map-support": "^0.5.21"
42 } 40 }
43} 41}
diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts
index 29a8cca..78861c2 100644
--- a/packages/main/src/index.ts
+++ b/packages/main/src/index.ts
@@ -22,10 +22,11 @@
22import os from 'node:os'; 22import os from 'node:os';
23 23
24import { app } from 'electron'; 24import { app } from 'electron';
25import { ensureDirSync } from 'fs-extra';
26import osName from 'os-name';
27 25
28import { enableStacktraceSourceMaps } from './infrastructure/electron/impl/devTools'; 26import {
27 enableStacktraceSourceMaps,
28 ensureDevDataDir,
29} from './infrastructure/electron/impl/devTools';
29import electronShell from './infrastructure/electron/impl/electronShell'; 30import electronShell from './infrastructure/electron/impl/electronShell';
30import initReactions from './initReactions'; 31import initReactions from './initReactions';
31import MainStore from './stores/MainStore'; 32import MainStore from './stores/MainStore';
@@ -39,11 +40,7 @@ const log = getLogger('index');
39app.enableSandbox(); 40app.enableSandbox();
40 41
41if (isDevelopment) { 42if (isDevelopment) {
42 // Use alternative directory when debugging to avoid clobbering the main installation. 43 ensureDevDataDir();
43 app.setPath('userData', `${app.getPath('userData')}-dev`);
44 ensureDirSync(app.getPath('userData'));
45
46 // Use source maps in stack traces.
47 enableStacktraceSourceMaps(); 44 enableStacktraceSourceMaps();
48} 45}
49 46
@@ -67,7 +64,7 @@ app.setAboutPanelOptions({
67 `Electron: ${process.versions.electron}`, 64 `Electron: ${process.versions.electron}`,
68 `Chrome: ${process.versions.chrome}`, 65 `Chrome: ${process.versions.chrome}`,
69 `Node.js: ${process.versions.node}`, 66 `Node.js: ${process.versions.node}`,
70 `Platform: ${osName()}`, 67 `Platform: ${os.platform()} ${os.release()}`,
71 `Arch: ${os.arch()}`, 68 `Arch: ${os.arch()}`,
72 `Build date: ${new Date( 69 `Build date: ${new Date(
73 Number(import.meta.env.BUILD_DATE), 70 Number(import.meta.env.BUILD_DATE),
diff --git a/packages/main/src/infrastructure/config/ConfigRepository.ts b/packages/main/src/infrastructure/config/ConfigRepository.ts
index e00f5a0..67bffb0 100644
--- a/packages/main/src/infrastructure/config/ConfigRepository.ts
+++ b/packages/main/src/infrastructure/config/ConfigRepository.ts
@@ -18,17 +18,16 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import type Config from '../../stores/config/Config';
22import type Disposer from '../../utils/Disposer'; 21import type Disposer from '../../utils/Disposer';
23 22
24export type ReadConfigResult = 23export type ReadConfigResult =
25 | { found: true; data: unknown } 24 | { found: true; contents: string }
26 | { found: false }; 25 | { found: false };
27 26
28export default interface ConfigPersistence { 27export default interface ConfigPersistence {
29 readConfig(): Promise<ReadConfigResult>; 28 readConfig(): Promise<ReadConfigResult>;
30 29
31 writeConfig(config: Config): Promise<void>; 30 writeConfig(contents: string): Promise<void>;
32 31
33 watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer; 32 watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer;
34} 33}
diff --git a/packages/main/src/infrastructure/config/impl/ConfigFile.ts b/packages/main/src/infrastructure/config/impl/ConfigFile.ts
index c4e6d22..8b110a2 100644
--- a/packages/main/src/infrastructure/config/impl/ConfigFile.ts
+++ b/packages/main/src/infrastructure/config/impl/ConfigFile.ts
@@ -24,7 +24,6 @@ import path from 'node:path';
24 24
25import { throttle } from 'lodash-es'; 25import { throttle } from 'lodash-es';
26 26
27import type Config from '../../../stores/config/Config';
28import type Disposer from '../../../utils/Disposer'; 27import type Disposer from '../../../utils/Disposer';
29import isErrno from '../../../utils/isErrno'; 28import isErrno from '../../../utils/isErrno';
30import { getLogger } from '../../../utils/log'; 29import { getLogger } from '../../../utils/log';
@@ -48,9 +47,9 @@ export default class ConfigFile implements ConfigRepository {
48 } 47 }
49 48
50 async readConfig(): Promise<ReadConfigResult> { 49 async readConfig(): Promise<ReadConfigResult> {
51 let configStr: string; 50 let contents: string;
52 try { 51 try {
53 configStr = await readFile(this.configFilePath, 'utf8'); 52 contents = await readFile(this.configFilePath, 'utf8');
54 } catch (error) { 53 } catch (error) {
55 if (isErrno(error, 'ENOENT')) { 54 if (isErrno(error, 'ENOENT')) {
56 log.debug('Config file', this.configFilePath, 'was not found'); 55 log.debug('Config file', this.configFilePath, 'was not found');
@@ -61,15 +60,14 @@ export default class ConfigFile implements ConfigRepository {
61 log.info('Read config file', this.configFilePath); 60 log.info('Read config file', this.configFilePath);
62 return { 61 return {
63 found: true, 62 found: true,
64 data: JSON.parse(configStr), 63 contents,
65 }; 64 };
66 } 65 }
67 66
68 async writeConfig(configSnapshot: Config): Promise<void> { 67 async writeConfig(contents: string): Promise<void> {
69 const configJson = JSON.stringify(configSnapshot, undefined, 2);
70 this.writingConfig = true; 68 this.writingConfig = true;
71 try { 69 try {
72 await writeFile(this.configFilePath, configJson, 'utf8'); 70 await writeFile(this.configFilePath, contents, 'utf8');
73 const { mtime } = await stat(this.configFilePath); 71 const { mtime } = await stat(this.configFilePath);
74 log.trace('Config file', this.configFilePath, 'last written at', mtime); 72 log.trace('Config file', this.configFilePath, 'last written at', mtime);
75 this.timeLastWritten = mtime; 73 this.timeLastWritten = mtime;
diff --git a/packages/main/src/infrastructure/electron/impl/devTools.ts b/packages/main/src/infrastructure/electron/impl/devTools.ts
index 10f4545..6db88d1 100644
--- a/packages/main/src/infrastructure/electron/impl/devTools.ts
+++ b/packages/main/src/infrastructure/electron/impl/devTools.ts
@@ -18,34 +18,32 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import type { BrowserWindow } from 'electron'; 21import { app, type BrowserWindow } from 'electron';
22
23/* eslint-disable
24 import/no-extraneous-dependencies,
25 global-require,
26 @typescript-eslint/no-var-requires,
27 unicorn/prefer-module --
28 Hack to lazily require a CJS module from an ES module transpiled into a CJS module.
29*/
22 30
23/** 31/**
24 * URL prefixes Sophie is allowed load in dev mode. 32 * Makes sure we use a separate data dir for development.
25 *
26 * In dev mode, in addition to the application itself,
27 * Sophie must be able do download and load the devtools and related extensions,
28 * so we have to make exceptions in the UI process request filter.
29 */ 33 */
30export const DEVMODE_ALLOWED_URL_PREFIXES = [ 34export function ensureDevDataDir(): void {
31 'chrome-extension:', 35 // Use alternative directory when debugging to avoid clobbering the main installation.
32 'devtools:', 36 app.setPath('userData', `${app.getPath('userData')}-dev`);
33 'https://clients2.google.com/service/update2/crx', 37 const userData = app.getPath('userData');
34 'https://clients2.googleusercontent.com/crx', 38 const mkdirp = require('mkdirp') as typeof import('mkdirp');
35]; 39 mkdirp.sync(userData);
40}
36 41
37/** 42/**
38 * Enables using source maps for node stack traces. 43 * Enables using source maps for node stack traces.
39 */ 44 */
40export function enableStacktraceSourceMaps(): void { 45export function enableStacktraceSourceMaps(): void {
41 const sourceMapSupport = 46 const sourceMapSupport =
42 /* eslint-disable-next-line
43 import/no-extraneous-dependencies,
44 global-require,
45 @typescript-eslint/no-var-requires,
46 unicorn/prefer-module --
47 Hack to lazily require a CJS module from an ES module transpiled into a CJS module.
48 */
49 require('source-map-support') as typeof import('source-map-support'); 47 require('source-map-support') as typeof import('source-map-support');
50 sourceMapSupport.install(); 48 sourceMapSupport.install();
51} 49}
@@ -61,13 +59,6 @@ export async function installDevToolsExtensions(): Promise<void> {
61 default: installExtension, 59 default: installExtension,
62 REACT_DEVELOPER_TOOLS, 60 REACT_DEVELOPER_TOOLS,
63 REDUX_DEVTOOLS, 61 REDUX_DEVTOOLS,
64 /* eslint-disable-next-line
65 import/no-extraneous-dependencies,
66 global-require,
67 @typescript-eslint/no-var-requires,
68 unicorn/prefer-module --
69 Hack to lazily require a CJS module from an ES module transpiled into a CJS module.
70 */
71 } = require('electron-devtools-installer') as typeof import('electron-devtools-installer'); 62 } = require('electron-devtools-installer') as typeof import('electron-devtools-installer');
72 await installExtension([REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS], { 63 await installExtension([REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS], {
73 forceDownload: false, 64 forceDownload: false,
diff --git a/packages/main/src/infrastructure/electron/impl/hardenSession.ts b/packages/main/src/infrastructure/electron/impl/hardenSession.ts
index 10b694a..53675a7 100644
--- a/packages/main/src/infrastructure/electron/impl/hardenSession.ts
+++ b/packages/main/src/infrastructure/electron/impl/hardenSession.ts
@@ -25,7 +25,19 @@ import type { Session } from 'electron';
25import { getLogger } from '../../../utils/log'; 25import { getLogger } from '../../../utils/log';
26import type Resources from '../../resources/Resources'; 26import type Resources from '../../resources/Resources';
27 27
28import { DEVMODE_ALLOWED_URL_PREFIXES } from './devTools'; 28/**
29 * URL prefixes Sophie is allowed load in dev mode.
30 *
31 * In dev mode, in addition to the application itself,
32 * Sophie must be able do download and load the devtools and related extensions,
33 * so we have to make exceptions in the UI process request filter.
34 */
35export const DEVMODE_ALLOWED_URL_PREFIXES = [
36 'chrome-extension:',
37 'devtools:',
38 'https://clients2.google.com/service/update2/crx',
39 'https://clients2.googleusercontent.com/crx',
40];
29 41
30const log = getLogger('hardenSession'); 42const log = getLogger('hardenSession');
31 43
diff --git a/packages/main/src/reactions/__tests__/synchronizeConfig.spec.ts b/packages/main/src/reactions/__tests__/synchronizeConfig.spec.ts
index b5013ea..d3338d0 100644
--- a/packages/main/src/reactions/__tests__/synchronizeConfig.spec.ts
+++ b/packages/main/src/reactions/__tests__/synchronizeConfig.spec.ts
@@ -25,7 +25,7 @@ import type ConfigRepository from '../../infrastructure/config/ConfigRepository'
25import SharedStore from '../../stores/SharedStore'; 25import SharedStore from '../../stores/SharedStore';
26import type Disposer from '../../utils/Disposer'; 26import type Disposer from '../../utils/Disposer';
27import { silenceLogger } from '../../utils/log'; 27import { silenceLogger } from '../../utils/log';
28import synchronizeConfig from '../synchronizeConfig'; 28import synchronizeConfig, { serializeConfig } from '../synchronizeConfig';
29 29
30let store: SharedStore; 30let store: SharedStore;
31const repository: ConfigRepository = { 31const repository: ConfigRepository = {
@@ -70,11 +70,11 @@ describe('when synchronizeializing', () => {
70 beforeEach(() => { 70 beforeEach(() => {
71 mocked(repository.readConfig).mockResolvedValueOnce({ 71 mocked(repository.readConfig).mockResolvedValueOnce({
72 found: true, 72 found: true,
73 data: { 73 contents: serializeConfig({
74 // Use a default empty config file to not trigger config rewrite. 74 // Use a default empty config file to not trigger config rewrite.
75 ...store.config, 75 ...store.config,
76 themeSource: 'dark', 76 themeSource: 'dark',
77 }, 77 }),
78 }); 78 });
79 }); 79 });
80 80
@@ -97,12 +97,15 @@ describe('when synchronizeializing', () => {
97 it('should update the config file if new details are added during read', async () => { 97 it('should update the config file if new details are added during read', async () => {
98 mocked(repository.readConfig).mockResolvedValueOnce({ 98 mocked(repository.readConfig).mockResolvedValueOnce({
99 found: true, 99 found: true,
100 data: { 100 contents: `{
101 themeSource: 'light', 101 "themeSource": "light",
102 profile: { 102 "profiles": [
103 name: 'Test profile', 103 {
104 }, 104 "name": "Test profile"
105 }, 105 }
106 ]
107}
108`,
106 }); 109 });
107 await synchronizeConfig(store, repository); 110 await synchronizeConfig(store, repository);
108 expect(repository.writeConfig).toHaveBeenCalledTimes(1); 111 expect(repository.writeConfig).toHaveBeenCalledTimes(1);
@@ -111,9 +114,10 @@ describe('when synchronizeializing', () => {
111 it('should not apply an invalid config file but should not overwrite it', async () => { 114 it('should not apply an invalid config file but should not overwrite it', async () => {
112 mocked(repository.readConfig).mockResolvedValueOnce({ 115 mocked(repository.readConfig).mockResolvedValueOnce({
113 found: true, 116 found: true,
114 data: { 117 contents: `{
115 themeSource: -1, 118 "themeSource": -1
116 }, 119}
120`,
117 }); 121 });
118 await synchronizeConfig(store, repository); 122 await synchronizeConfig(store, repository);
119 expect(store.settings.themeSource).not.toBe(-1); 123 expect(store.settings.themeSource).not.toBe(-1);
@@ -136,7 +140,7 @@ describe('when it has loaded the config', () => {
136 beforeEach(async () => { 140 beforeEach(async () => {
137 mocked(repository.readConfig).mockResolvedValueOnce({ 141 mocked(repository.readConfig).mockResolvedValueOnce({
138 found: true, 142 found: true,
139 data: store.config, 143 contents: serializeConfig(store.config),
140 }); 144 });
141 mocked(repository.watchConfig).mockReturnValueOnce(watcherDisposer); 145 mocked(repository.watchConfig).mockReturnValueOnce(watcherDisposer);
142 sutDisposer = await synchronizeConfig(store, repository, throttleMs); 146 sutDisposer = await synchronizeConfig(store, repository, throttleMs);
@@ -163,11 +167,11 @@ describe('when it has loaded the config', () => {
163 it('should read the config file when it has changed', async () => { 167 it('should read the config file when it has changed', async () => {
164 mocked(repository.readConfig).mockResolvedValueOnce({ 168 mocked(repository.readConfig).mockResolvedValueOnce({
165 found: true, 169 found: true,
166 data: { 170 contents: serializeConfig({
167 // Use a default empty config file to not trigger config rewrite. 171 // Use a default empty config file to not trigger config rewrite.
168 ...store.config, 172 ...store.config,
169 themeSource: 'dark', 173 themeSource: 'dark',
170 }, 174 }),
171 }); 175 });
172 await configChangedCallback(); 176 await configChangedCallback();
173 // Do not write back the changes we have just read. 177 // Do not write back the changes we have just read.
@@ -178,12 +182,15 @@ describe('when it has loaded the config', () => {
178 it('should update the config file if new details are added', async () => { 182 it('should update the config file if new details are added', async () => {
179 mocked(repository.readConfig).mockResolvedValueOnce({ 183 mocked(repository.readConfig).mockResolvedValueOnce({
180 found: true, 184 found: true,
181 data: { 185 contents: `{
182 themeSource: 'light', 186 "themeSource": "light",
183 profile: { 187 "profiles": [
184 name: 'Test profile', 188 {
185 }, 189 "name": "Test profile"
186 }, 190 }
191 ]
192}
193`,
187 }); 194 });
188 await configChangedCallback(); 195 await configChangedCallback();
189 expect(repository.writeConfig).toHaveBeenCalledTimes(1); 196 expect(repository.writeConfig).toHaveBeenCalledTimes(1);
@@ -192,9 +199,10 @@ describe('when it has loaded the config', () => {
192 it('should not apply an invalid config file when it has changed but should not overwrite it', async () => { 199 it('should not apply an invalid config file when it has changed but should not overwrite it', async () => {
193 mocked(repository.readConfig).mockResolvedValueOnce({ 200 mocked(repository.readConfig).mockResolvedValueOnce({
194 found: true, 201 found: true,
195 data: { 202 contents: `{
196 themeSource: -1, 203 "themeSource": -1
197 }, 204}
205`,
198 }); 206 });
199 await configChangedCallback(); 207 await configChangedCallback();
200 expect(store.settings.themeSource).not.toBe(-1); 208 expect(store.settings.themeSource).not.toBe(-1);
diff --git a/packages/main/src/reactions/synchronizeConfig.ts b/packages/main/src/reactions/synchronizeConfig.ts
index 7e366e2..247c2e2 100644
--- a/packages/main/src/reactions/synchronizeConfig.ts
+++ b/packages/main/src/reactions/synchronizeConfig.ts
@@ -18,7 +18,6 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import deepEqual from 'deep-equal';
22import { debounce } from 'lodash-es'; 21import { debounce } from 'lodash-es';
23import { reaction } from 'mobx'; 22import { reaction } from 'mobx';
24 23
@@ -32,36 +31,38 @@ const DEFAULT_CONFIG_DEBOUNCE_TIME_MS = 1000;
32 31
33const log = getLogger('synchronizeConfig'); 32const log = getLogger('synchronizeConfig');
34 33
34export function serializeConfig(config: Config): string {
35 return `${JSON.stringify(config, undefined, 2)}\n`;
36}
37
35export default async function synchronizeConfig( 38export default async function synchronizeConfig(
36 sharedStore: SharedStore, 39 sharedStore: SharedStore,
37 repository: ConfigRepository, 40 repository: ConfigRepository,
38 debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME_MS, 41 debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME_MS,
39): Promise<Disposer> { 42): Promise<Disposer> {
40 let lastConfigOnDisk: Config | undefined; 43 let lastConfigOnDisk: string | undefined;
41 44
42 async function writeConfig(): Promise<void> { 45 async function writeConfig(serializedConfig: string): Promise<void> {
43 const { config } = sharedStore; 46 await repository.writeConfig(serializedConfig);
44 await repository.writeConfig(config); 47 lastConfigOnDisk = serializedConfig;
45 lastConfigOnDisk = config;
46 } 48 }
47 49
48 async function readConfig(): Promise<boolean> { 50 async function readConfig(): Promise<boolean> {
49 const result = await repository.readConfig(); 51 const result = await repository.readConfig();
50 if (result.found) { 52 if (result.found) {
53 const { contents } = result;
51 try { 54 try {
52 // This cast is unsound if the config file is invalid, 55 // This cast is unsound if the config file is invalid,
53 // but we'll throw an error in the end anyways. 56 // but we'll throw an error in the end anyways.
54 sharedStore.loadConfig(result.data as Config); 57 const data = JSON.parse(contents) as Config;
58 sharedStore.loadConfig(data);
55 } catch (error) { 59 } catch (error) {
56 log.error('Failed to apply config snapshot', result.data, error); 60 log.error('Failed to apply config snapshot', contents, error);
57 return true; 61 return true;
58 } 62 }
59 lastConfigOnDisk = sharedStore.config; 63 lastConfigOnDisk = serializeConfig(sharedStore.config);
60 // We can't use `comparer.structural` from `mobx`, because 64 if (contents !== lastConfigOnDisk) {
61 // it handles missing values and `undefined` values differently, 65 await writeConfig(lastConfigOnDisk);
62 // but JSON is unable to distinguish them.
63 if (!deepEqual(result.data, lastConfigOnDisk, { strict: true })) {
64 await writeConfig();
65 } 66 }
66 } 67 }
67 return result.found; 68 return result.found;
@@ -69,16 +70,17 @@ export default async function synchronizeConfig(
69 70
70 if (!(await readConfig())) { 71 if (!(await readConfig())) {
71 log.info('Config file was not found'); 72 log.info('Config file was not found');
72 await writeConfig(); 73 const serializedConfig = serializeConfig(sharedStore.config);
74 await writeConfig(serializedConfig);
73 log.info('Created config file'); 75 log.info('Created config file');
74 } 76 }
75 77
76 const disposeReaction = reaction( 78 const disposeReaction = reaction(
77 () => sharedStore.config, 79 () => sharedStore.config,
78 debounce((config) => { 80 debounce(() => {
79 // We can compare snapshots by reference, since it is only recreated on store changes. 81 const serializedConfig = serializeConfig(sharedStore.config);
80 if (!deepEqual(config, lastConfigOnDisk, { strict: true })) { 82 if (serializedConfig !== lastConfigOnDisk) {
81 writeConfig().catch((error) => { 83 writeConfig(serializedConfig).catch((error) => {
82 log.error('Failed to write config on config change', error); 84 log.error('Failed to write config on config change', error);
83 }); 85 });
84 } 86 }
diff --git a/yarn.lock b/yarn.lock
index 0c26d14..a62cb65 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1264,18 +1264,16 @@ __metadata:
1264 "@sophie/service-shared": "workspace:*" 1264 "@sophie/service-shared": "workspace:*"
1265 "@sophie/shared": "workspace:*" 1265 "@sophie/shared": "workspace:*"
1266 "@sophie/test-utils": "workspace:*" 1266 "@sophie/test-utils": "workspace:*"
1267 "@types/deep-equal": ^1.0.1
1268 "@types/electron-devtools-installer": ^2.2.2 1267 "@types/electron-devtools-installer": ^2.2.2
1269 "@types/lodash-es": ^4.17.6 1268 "@types/lodash-es": ^4.17.6
1269 "@types/mkdirp": ^1
1270 "@types/node": ^17.0.25 1270 "@types/node": ^17.0.25
1271 "@types/slug": ^5.0.3 1271 "@types/slug": ^5.0.3
1272 "@types/source-map-support": ^0.5.4 1272 "@types/source-map-support": ^0.5.4
1273 chalk: ^5.0.1 1273 chalk: ^5.0.1
1274 deep-equal: ^2.0.5
1275 electron: ^19.0.0-alpha.1 1274 electron: ^19.0.0-alpha.1
1276 electron-devtools-installer: ^3.2.0 1275 electron-devtools-installer: ^3.2.0
1277 esbuild: ^0.14.38 1276 esbuild: ^0.14.38
1278 fs-extra: ^10.1.0
1279 git-repo-info: ^2.1.1 1277 git-repo-info: ^2.1.1
1280 i18next: ^21.6.16 1278 i18next: ^21.6.16
1281 jest: ^27.5.1 1279 jest: ^27.5.1
@@ -1283,10 +1281,10 @@ __metadata:
1283 lodash-es: ^4.17.21 1281 lodash-es: ^4.17.21
1284 loglevel: ^1.8.0 1282 loglevel: ^1.8.0
1285 loglevel-plugin-prefix: ^0.8.4 1283 loglevel-plugin-prefix: ^0.8.4
1284 mkdirp: ^1.0.4
1286 mobx: ^6.5.0 1285 mobx: ^6.5.0
1287 mobx-state-tree: ^5.1.3 1286 mobx-state-tree: ^5.1.3
1288 nanoid: ^3.3.3 1287 nanoid: ^3.3.3
1289 os-name: ^5.0.1
1290 slug: ^5.3.0 1288 slug: ^5.3.0
1291 source-map-support: ^0.5.21 1289 source-map-support: ^0.5.21
1292 languageName: unknown 1290 languageName: unknown
@@ -1478,13 +1476,6 @@ __metadata:
1478 languageName: node 1476 languageName: node
1479 linkType: hard 1477 linkType: hard
1480 1478
1481"@types/deep-equal@npm:^1.0.1":
1482 version: 1.0.1
1483 resolution: "@types/deep-equal@npm:1.0.1"
1484 checksum: 689b5737dd0a37d173d9e1231c07f70a1a9a989087b757422e1d167ec3640fd7eb8a538bd6008b3df400c4c11ed07a6c2a92d9aafc1f98ffaa04731a9442c781
1485 languageName: node
1486 linkType: hard
1487
1488"@types/electron-devtools-installer@npm:^2.2.2": 1479"@types/electron-devtools-installer@npm:^2.2.2":
1489 version: 2.2.2 1480 version: 2.2.2
1490 resolution: "@types/electron-devtools-installer@npm:2.2.2" 1481 resolution: "@types/electron-devtools-installer@npm:2.2.2"
@@ -1610,6 +1601,15 @@ __metadata:
1610 languageName: node 1601 languageName: node
1611 linkType: hard 1602 linkType: hard
1612 1603
1604"@types/mkdirp@npm:^1":
1605 version: 1.0.2
1606 resolution: "@types/mkdirp@npm:1.0.2"
1607 dependencies:
1608 "@types/node": "*"
1609 checksum: 72dedfb2d250f0e44ec964ac68ff6a4a03d7d9d6e83ae1d5ba6d212791af69cf051a5fac59846826242d099de56a18c8e0581861792fae8b845b3c9ad67ecdeb
1610 languageName: node
1611 linkType: hard
1612
1613"@types/ms@npm:*": 1613"@types/ms@npm:*":
1614 version: 0.7.31 1614 version: 0.7.31
1615 resolution: "@types/ms@npm:0.7.31" 1615 resolution: "@types/ms@npm:0.7.31"
@@ -2287,13 +2287,6 @@ __metadata:
2287 languageName: node 2287 languageName: node
2288 linkType: hard 2288 linkType: hard
2289 2289
2290"available-typed-arrays@npm:^1.0.5":
2291 version: 1.0.5
2292 resolution: "available-typed-arrays@npm:1.0.5"
2293 checksum: 20eb47b3cefd7db027b9bbb993c658abd36d4edd3fe1060e83699a03ee275b0c9b216cc076ff3f2db29073225fb70e7613987af14269ac1fe2a19803ccc97f1a
2294 languageName: node
2295 linkType: hard
2296
2297"axe-core@npm:^4.3.5": 2290"axe-core@npm:^4.3.5":
2298 version: 4.3.5 2291 version: 4.3.5
2299 resolution: "axe-core@npm:4.3.5" 2292 resolution: "axe-core@npm:4.3.5"
@@ -3211,29 +3204,6 @@ __metadata:
3211 languageName: node 3204 languageName: node
3212 linkType: hard 3205 linkType: hard
3213 3206
3214"deep-equal@npm:^2.0.5":
3215 version: 2.0.5
3216 resolution: "deep-equal@npm:2.0.5"
3217 dependencies:
3218 call-bind: ^1.0.0
3219 es-get-iterator: ^1.1.1
3220 get-intrinsic: ^1.0.1
3221 is-arguments: ^1.0.4
3222 is-date-object: ^1.0.2
3223 is-regex: ^1.1.1
3224 isarray: ^2.0.5
3225 object-is: ^1.1.4
3226 object-keys: ^1.1.1
3227 object.assign: ^4.1.2
3228 regexp.prototype.flags: ^1.3.0
3229 side-channel: ^1.0.3
3230 which-boxed-primitive: ^1.0.1
3231 which-collection: ^1.0.1
3232 which-typed-array: ^1.1.2
3233 checksum: 2bb7332badf589b540184d25098acac750e30fe11c8dce4523d03fc5db15f46881a0105e6bf0b64bb0c57213a95ed964029ff0259026ad6f7f9e0019f8200de5
3234 languageName: node
3235 linkType: hard
3236
3237"deep-extend@npm:^0.6.0": 3207"deep-extend@npm:^0.6.0":
3238 version: 0.6.0 3208 version: 0.6.0
3239 resolution: "deep-extend@npm:0.6.0" 3209 resolution: "deep-extend@npm:0.6.0"
@@ -3615,7 +3585,7 @@ __metadata:
3615 languageName: node 3585 languageName: node
3616 linkType: hard 3586 linkType: hard
3617 3587
3618"es-abstract@npm:^1.18.5, es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1": 3588"es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1":
3619 version: 1.19.1 3589 version: 1.19.1
3620 resolution: "es-abstract@npm:1.19.1" 3590 resolution: "es-abstract@npm:1.19.1"
3621 dependencies: 3591 dependencies:
@@ -3643,22 +3613,6 @@ __metadata:
3643 languageName: node 3613 languageName: node
3644 linkType: hard 3614 linkType: hard
3645 3615
3646"es-get-iterator@npm:^1.1.1":
3647 version: 1.1.2
3648 resolution: "es-get-iterator@npm:1.1.2"
3649 dependencies:
3650 call-bind: ^1.0.2
3651 get-intrinsic: ^1.1.0
3652 has-symbols: ^1.0.1
3653 is-arguments: ^1.1.0
3654 is-map: ^2.0.2
3655 is-set: ^2.0.2
3656 is-string: ^1.0.5
3657 isarray: ^2.0.5
3658 checksum: f75e66acb6a45686fa08b3ade9c9421a70d36a0c43ed4363e67f4d7aab2226cb73dd977cb48abbaf75721b946d3cd810682fcf310c7ad0867802fbf929b17dcf
3659 languageName: node
3660 linkType: hard
3661
3662"es-to-primitive@npm:^1.2.1": 3616"es-to-primitive@npm:^1.2.1":
3663 version: 1.2.1 3617 version: 1.2.1
3664 resolution: "es-to-primitive@npm:1.2.1" 3618 resolution: "es-to-primitive@npm:1.2.1"
@@ -4356,7 +4310,7 @@ __metadata:
4356 languageName: node 4310 languageName: node
4357 linkType: hard 4311 linkType: hard
4358 4312
4359"execa@npm:^5.0.0, execa@npm:^5.1.1": 4313"execa@npm:^5.0.0":
4360 version: 5.1.1 4314 version: 5.1.1
4361 resolution: "execa@npm:5.1.1" 4315 resolution: "execa@npm:5.1.1"
4362 dependencies: 4316 dependencies:
@@ -4551,13 +4505,6 @@ __metadata:
4551 languageName: node 4505 languageName: node
4552 linkType: hard 4506 linkType: hard
4553 4507
4554"foreach@npm:^2.0.5":
4555 version: 2.0.5
4556 resolution: "foreach@npm:2.0.5"
4557 checksum: dab4fbfef0b40b69ee5eab81bcb9626b8fa8b3469c8cfa26480f3e5e1ee08c40eae07048c9a967c65aeda26e774511ccc70b3f10a604c01753c6ef24361f0fc8
4558 languageName: node
4559 linkType: hard
4560
4561"form-data@npm:^3.0.0": 4508"form-data@npm:^3.0.0":
4562 version: 3.0.1 4509 version: 3.0.1
4563 resolution: "form-data@npm:3.0.1" 4510 resolution: "form-data@npm:3.0.1"
@@ -4580,7 +4527,7 @@ __metadata:
4580 languageName: node 4527 languageName: node
4581 linkType: hard 4528 linkType: hard
4582 4529
4583"fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0": 4530"fs-extra@npm:^10.0.0":
4584 version: 10.1.0 4531 version: 10.1.0
4585 resolution: "fs-extra@npm:10.1.0" 4532 resolution: "fs-extra@npm:10.1.0"
4586 dependencies: 4533 dependencies:
@@ -4694,7 +4641,7 @@ __metadata:
4694 languageName: node 4641 languageName: node
4695 linkType: hard 4642 linkType: hard
4696 4643
4697"get-intrinsic@npm:^1.0.1, get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": 4644"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1":
4698 version: 1.1.1 4645 version: 1.1.1
4699 resolution: "get-intrinsic@npm:1.1.1" 4646 resolution: "get-intrinsic@npm:1.1.1"
4700 dependencies: 4647 dependencies:
@@ -5224,16 +5171,6 @@ __metadata:
5224 languageName: node 5171 languageName: node
5225 linkType: hard 5172 linkType: hard
5226 5173
5227"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.0":
5228 version: 1.1.1
5229 resolution: "is-arguments@npm:1.1.1"
5230 dependencies:
5231 call-bind: ^1.0.2
5232 has-tostringtag: ^1.0.0
5233 checksum: 7f02700ec2171b691ef3e4d0e3e6c0ba408e8434368504bb593d0d7c891c0dbfda6d19d30808b904a6cb1929bca648c061ba438c39f296c2a8ca083229c49f27
5234 languageName: node
5235 linkType: hard
5236
5237"is-arrayish@npm:^0.2.1": 5174"is-arrayish@npm:^0.2.1":
5238 version: 0.2.1 5175 version: 0.2.1
5239 resolution: "is-arrayish@npm:0.2.1" 5176 resolution: "is-arrayish@npm:0.2.1"
@@ -5316,7 +5253,7 @@ __metadata:
5316 languageName: node 5253 languageName: node
5317 linkType: hard 5254 linkType: hard
5318 5255
5319"is-date-object@npm:^1.0.1, is-date-object@npm:^1.0.2": 5256"is-date-object@npm:^1.0.1":
5320 version: 1.0.5 5257 version: 1.0.5
5321 resolution: "is-date-object@npm:1.0.5" 5258 resolution: "is-date-object@npm:1.0.5"
5322 dependencies: 5259 dependencies:
@@ -5372,13 +5309,6 @@ __metadata:
5372 languageName: node 5309 languageName: node
5373 linkType: hard 5310 linkType: hard
5374 5311
5375"is-map@npm:^2.0.1, is-map@npm:^2.0.2":
5376 version: 2.0.2
5377 resolution: "is-map@npm:2.0.2"
5378 checksum: ace3d0ecd667bbdefdb1852de601268f67f2db725624b1958f279316e13fecb8fa7df91fd60f690d7417b4ec180712f5a7ee967008e27c65cfd475cc84337728
5379 languageName: node
5380 linkType: hard
5381
5382"is-negative-zero@npm:^2.0.1": 5312"is-negative-zero@npm:^2.0.1":
5383 version: 2.0.2 5313 version: 2.0.2
5384 resolution: "is-negative-zero@npm:2.0.2" 5314 resolution: "is-negative-zero@npm:2.0.2"
@@ -5430,7 +5360,7 @@ __metadata:
5430 languageName: node 5360 languageName: node
5431 linkType: hard 5361 linkType: hard
5432 5362
5433"is-regex@npm:^1.1.1, is-regex@npm:^1.1.4": 5363"is-regex@npm:^1.1.4":
5434 version: 1.1.4 5364 version: 1.1.4
5435 resolution: "is-regex@npm:1.1.4" 5365 resolution: "is-regex@npm:1.1.4"
5436 dependencies: 5366 dependencies:
@@ -5440,13 +5370,6 @@ __metadata:
5440 languageName: node 5370 languageName: node
5441 linkType: hard 5371 linkType: hard
5442 5372
5443"is-set@npm:^2.0.1, is-set@npm:^2.0.2":
5444 version: 2.0.2
5445 resolution: "is-set@npm:2.0.2"
5446 checksum: b64343faf45e9387b97a6fd32be632ee7b269bd8183701f3b3f5b71a7cf00d04450ed8669d0bd08753e08b968beda96fca73a10fd0ff56a32603f64deba55a57
5447 languageName: node
5448 linkType: hard
5449
5450"is-shared-array-buffer@npm:^1.0.1": 5373"is-shared-array-buffer@npm:^1.0.1":
5451 version: 1.0.1 5374 version: 1.0.1
5452 resolution: "is-shared-array-buffer@npm:1.0.1" 5375 resolution: "is-shared-array-buffer@npm:1.0.1"
@@ -5479,19 +5402,6 @@ __metadata:
5479 languageName: node 5402 languageName: node
5480 linkType: hard 5403 linkType: hard
5481 5404
5482"is-typed-array@npm:^1.1.7":
5483 version: 1.1.8
5484 resolution: "is-typed-array@npm:1.1.8"
5485 dependencies:
5486 available-typed-arrays: ^1.0.5
5487 call-bind: ^1.0.2
5488 es-abstract: ^1.18.5
5489 foreach: ^2.0.5
5490 has-tostringtag: ^1.0.0
5491 checksum: aa0f9f0716e19e2fb8aef69e69e4205479d25ace778e2339fc910948115cde4b0d9aff9d5d1e8b80f09a5664998278e05e54ad3dc9cb12cefcf86db71084ed00
5492 languageName: node
5493 linkType: hard
5494
5495"is-typedarray@npm:^1.0.0": 5405"is-typedarray@npm:^1.0.0":
5496 version: 1.0.0 5406 version: 1.0.0
5497 resolution: "is-typedarray@npm:1.0.0" 5407 resolution: "is-typedarray@npm:1.0.0"
@@ -5499,13 +5409,6 @@ __metadata:
5499 languageName: node 5409 languageName: node
5500 linkType: hard 5410 linkType: hard
5501 5411
5502"is-weakmap@npm:^2.0.1":
5503 version: 2.0.1
5504 resolution: "is-weakmap@npm:2.0.1"
5505 checksum: 1222bb7e90c32bdb949226e66d26cb7bce12e1e28e3e1b40bfa6b390ba3e08192a8664a703dff2a00a84825f4e022f9cd58c4599ff9981ab72b1d69479f4f7f6
5506 languageName: node
5507 linkType: hard
5508
5509"is-weakref@npm:^1.0.1": 5412"is-weakref@npm:^1.0.1":
5510 version: 1.0.2 5413 version: 1.0.2
5511 resolution: "is-weakref@npm:1.0.2" 5414 resolution: "is-weakref@npm:1.0.2"
@@ -5515,16 +5418,6 @@ __metadata:
5515 languageName: node 5418 languageName: node
5516 linkType: hard 5419 linkType: hard
5517 5420
5518"is-weakset@npm:^2.0.1":
5519 version: 2.0.2
5520 resolution: "is-weakset@npm:2.0.2"
5521 dependencies:
5522 call-bind: ^1.0.2
5523 get-intrinsic: ^1.1.1
5524 checksum: 5d8698d1fa599a0635d7ca85be9c26d547b317ed8fd83fc75f03efbe75d50001b5eececb1e9971de85fcde84f69ae6f8346bc92d20d55d46201d328e4c74a367
5525 languageName: node
5526 linkType: hard
5527
5528"is-yarn-global@npm:^0.3.0": 5421"is-yarn-global@npm:^0.3.0":
5529 version: 0.3.0 5422 version: 0.3.0
5530 resolution: "is-yarn-global@npm:0.3.0" 5423 resolution: "is-yarn-global@npm:0.3.0"
@@ -5532,13 +5425,6 @@ __metadata:
5532 languageName: node 5425 languageName: node
5533 linkType: hard 5426 linkType: hard
5534 5427
5535"isarray@npm:^2.0.5":
5536 version: 2.0.5
5537 resolution: "isarray@npm:2.0.5"
5538 checksum: bd5bbe4104438c4196ba58a54650116007fa0262eccef13a4c55b2e09a5b36b59f1e75b9fcc49883dd9d4953892e6fc007eef9e9155648ceea036e184b0f930a
5539 languageName: node
5540 linkType: hard
5541
5542"isarray@npm:~1.0.0": 5428"isarray@npm:~1.0.0":
5543 version: 1.0.0 5429 version: 1.0.0
5544 resolution: "isarray@npm:1.0.0" 5430 resolution: "isarray@npm:1.0.0"
@@ -6550,13 +6436,6 @@ __metadata:
6550 languageName: node 6436 languageName: node
6551 linkType: hard 6437 linkType: hard
6552 6438
6553"macos-release@npm:^3.0.1":
6554 version: 3.0.1
6555 resolution: "macos-release@npm:3.0.1"
6556 checksum: 117c78e92b995bcd391cd7a35cbae122aae491ec6ef726257c7d58ab06087506b9c09d2c89c1dc11cf8fca5ade7dbfb18f8bed01702f5faf7ea5371e07405074
6557 languageName: node
6558 linkType: hard
6559
6560"make-dir@npm:^3.0.0": 6439"make-dir@npm:^3.0.0":
6561 version: 3.1.0 6440 version: 3.1.0
6562 resolution: "make-dir@npm:3.1.0" 6441 resolution: "make-dir@npm:3.1.0"
@@ -7020,16 +6899,6 @@ __metadata:
7020 languageName: node 6899 languageName: node
7021 linkType: hard 6900 linkType: hard
7022 6901
7023"object-is@npm:^1.1.4":
7024 version: 1.1.5
7025 resolution: "object-is@npm:1.1.5"
7026 dependencies:
7027 call-bind: ^1.0.2
7028 define-properties: ^1.1.3
7029 checksum: 989b18c4cba258a6b74dc1d74a41805c1a1425bce29f6cabb50dcb1a6a651ea9104a1b07046739a49a5bb1bc49727bcb00efd5c55f932f6ea04ec8927a7901fe
7030 languageName: node
7031 linkType: hard
7032
7033"object-keys@npm:^1.0.12, object-keys@npm:^1.1.1": 6902"object-keys@npm:^1.0.12, object-keys@npm:^1.1.1":
7034 version: 1.1.1 6903 version: 1.1.1
7035 resolution: "object-keys@npm:1.1.1" 6904 resolution: "object-keys@npm:1.1.1"
@@ -7138,16 +7007,6 @@ __metadata:
7138 languageName: node 7007 languageName: node
7139 linkType: hard 7008 linkType: hard
7140 7009
7141"os-name@npm:^5.0.1":
7142 version: 5.0.1
7143 resolution: "os-name@npm:5.0.1"
7144 dependencies:
7145 macos-release: ^3.0.1
7146 windows-release: ^5.0.1
7147 checksum: 7fe0db633aae8a030d0897655deede763a5b76a2b7b156e3f956e28b1c2ba9bb43565ce6b82cb4cf1c196673a80ca68b420d33450e29dd82911f3b37fc168142
7148 languageName: node
7149 linkType: hard
7150
7151"p-cancelable@npm:^1.0.0": 7010"p-cancelable@npm:^1.0.0":
7152 version: 1.1.0 7011 version: 1.1.0
7153 resolution: "p-cancelable@npm:1.1.0" 7012 resolution: "p-cancelable@npm:1.1.0"
@@ -7717,7 +7576,7 @@ __metadata:
7717 languageName: node 7576 languageName: node
7718 linkType: hard 7577 linkType: hard
7719 7578
7720"regexp.prototype.flags@npm:^1.3.0, regexp.prototype.flags@npm:^1.3.1": 7579"regexp.prototype.flags@npm:^1.3.1":
7721 version: 1.3.1 7580 version: 1.3.1
7722 resolution: "regexp.prototype.flags@npm:1.3.1" 7581 resolution: "regexp.prototype.flags@npm:1.3.1"
7723 dependencies: 7582 dependencies:
@@ -8097,7 +7956,7 @@ __metadata:
8097 languageName: node 7956 languageName: node
8098 linkType: hard 7957 linkType: hard
8099 7958
8100"side-channel@npm:^1.0.3, side-channel@npm:^1.0.4": 7959"side-channel@npm:^1.0.4":
8101 version: 1.0.4 7960 version: 1.0.4
8102 resolution: "side-channel@npm:1.0.4" 7961 resolution: "side-channel@npm:1.0.4"
8103 dependencies: 7962 dependencies:
@@ -9184,7 +9043,7 @@ __metadata:
9184 languageName: node 9043 languageName: node
9185 linkType: hard 9044 linkType: hard
9186 9045
9187"which-boxed-primitive@npm:^1.0.1, which-boxed-primitive@npm:^1.0.2": 9046"which-boxed-primitive@npm:^1.0.2":
9188 version: 1.0.2 9047 version: 1.0.2
9189 resolution: "which-boxed-primitive@npm:1.0.2" 9048 resolution: "which-boxed-primitive@npm:1.0.2"
9190 dependencies: 9049 dependencies:
@@ -9197,32 +9056,6 @@ __metadata:
9197 languageName: node 9056 languageName: node
9198 linkType: hard 9057 linkType: hard
9199 9058
9200"which-collection@npm:^1.0.1":
9201 version: 1.0.1
9202 resolution: "which-collection@npm:1.0.1"
9203 dependencies:
9204 is-map: ^2.0.1
9205 is-set: ^2.0.1
9206 is-weakmap: ^2.0.1
9207 is-weakset: ^2.0.1
9208 checksum: c815bbd163107ef9cb84f135e6f34453eaf4cca994e7ba85ddb0d27cea724c623fae2a473ceccfd5549c53cc65a5d82692de418166df3f858e1e5dc60818581c
9209 languageName: node
9210 linkType: hard
9211
9212"which-typed-array@npm:^1.1.2":
9213 version: 1.1.7
9214 resolution: "which-typed-array@npm:1.1.7"
9215 dependencies:
9216 available-typed-arrays: ^1.0.5
9217 call-bind: ^1.0.2
9218 es-abstract: ^1.18.5
9219 foreach: ^2.0.5
9220 has-tostringtag: ^1.0.0
9221 is-typed-array: ^1.1.7
9222 checksum: 147837cf5866e36b6b2e427731709e02f79f1578477cbde68ed773a5307520a6cb6836c73c79c30690a473266ee59010b83b6d9b25d8d677a40ff77fb37a8a84
9223 languageName: node
9224 linkType: hard
9225
9226"which@npm:^2.0.1, which@npm:^2.0.2": 9059"which@npm:^2.0.1, which@npm:^2.0.2":
9227 version: 2.0.2 9060 version: 2.0.2
9228 resolution: "which@npm:2.0.2" 9061 resolution: "which@npm:2.0.2"
@@ -9252,15 +9085,6 @@ __metadata:
9252 languageName: node 9085 languageName: node
9253 linkType: hard 9086 linkType: hard
9254 9087
9255"windows-release@npm:^5.0.1":
9256 version: 5.0.1
9257 resolution: "windows-release@npm:5.0.1"
9258 dependencies:
9259 execa: ^5.1.1
9260 checksum: b6b403333b7b3ea31a805c287f210962d8f3191865d81d2fd3955e603ab4d6893abc746d87b7da5b2a7a044b7b18df97c948e7d5392baed1d2bc5687fbf7431d
9261 languageName: node
9262 linkType: hard
9263
9264"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": 9088"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3":
9265 version: 1.2.3 9089 version: 1.2.3
9266 resolution: "word-wrap@npm:1.2.3" 9090 resolution: "word-wrap@npm:1.2.3"