From f634d11eeec8ecb6b868a1fa60cc2844bee6936a Mon Sep 17 00:00:00 2001 From: jdalton Date: Mon, 22 Dec 2025 13:15:27 -0500 Subject: [PATCH 1/5] feat(config): use EditableJson for non-destructive config saving Use EditableJson for preserving existing properties and key order when updating config values. This prevents overwriting unrelated config properties during partial updates. - Add standalone EditableJson implementation in src/utils/editable-json.mts - Update config.mts to use EditableJson for config file writes - Fix socketAppDataPath usage to include config.json filename - Add resetConfigForTesting() helper for test isolation - Update tests to use Node.js built-in fs functions --- src/utils/config.mts | 57 +++++- src/utils/config.test.mts | 14 +- src/utils/editable-json.mts | 381 ++++++++++++++++++++++++++++++++++++ 3 files changed, 443 insertions(+), 9 deletions(-) create mode 100644 src/utils/editable-json.mts diff --git a/src/utils/config.mts b/src/utils/config.mts index 3a8685638..9ff3b8063 100644 --- a/src/utils/config.mts +++ b/src/utils/config.mts @@ -31,6 +31,7 @@ import { logger } from '@socketsecurity/registry/lib/logger' import { naturalCompare } from '@socketsecurity/registry/lib/sorts' import { debugConfig } from './debug.mts' +import { getEditableJsonClass } from './editable-json.mts' import constants, { CONFIG_KEY_API_BASE_URL, CONFIG_KEY_API_PROXY, @@ -98,17 +99,18 @@ function getConfigValues(): LocalConfig { _cachedConfig = {} as LocalConfig const { socketAppDataPath } = constants if (socketAppDataPath) { - const raw = safeReadFileSync(socketAppDataPath) + const configFilePath = path.join(socketAppDataPath, 'config.json') + const raw = safeReadFileSync(configFilePath) if (raw) { try { Object.assign( _cachedConfig, JSON.parse(Buffer.from(raw, 'base64').toString()), ) - debugConfig(socketAppDataPath, true) + debugConfig(configFilePath, true) } catch (e) { - logger.warn(`Failed to parse config at ${socketAppDataPath}`) - debugConfig(socketAppDataPath, false, e) + logger.warn(`Failed to parse config at ${configFilePath}`) + debugConfig(configFilePath, false, e) } // Normalize apiKey to apiToken and persist it. // This is a one time migration per user. @@ -118,7 +120,7 @@ function getConfigValues(): LocalConfig { updateConfigValue(CONFIG_KEY_API_TOKEN, token) } } else { - mkdirSync(path.dirname(socketAppDataPath), { recursive: true }) + mkdirSync(socketAppDataPath, { recursive: true }) } } } @@ -243,6 +245,16 @@ let _cachedConfig: LocalConfig | undefined // When using --config or SOCKET_CLI_CONFIG, do not persist the config. let _configFromFlag = false +/** + * Reset config cache for testing purposes. + * This allows tests to start with a fresh config state. + * @internal + */ +export function resetConfigForTesting(): void { + _cachedConfig = undefined + _configFromFlag = false +} + export function overrideCachedConfig(jsonConfig: unknown): CResult { debugFn('notice', 'override: full config (not stored)') @@ -338,13 +350,44 @@ export function updateConfigValue( if (!_pendingSave) { _pendingSave = true + // Capture the current config state to save. + const configToSave = { ...localConfig } process.nextTick(() => { _pendingSave = false const { socketAppDataPath } = constants if (socketAppDataPath) { + mkdirSync(socketAppDataPath, { recursive: true }) + const configFilePath = path.join(socketAppDataPath, 'config.json') + // Read existing file to preserve formatting, then update with new values. + const existingRaw = safeReadFileSync(configFilePath) + const EditableJson = getEditableJsonClass() + const editor = new EditableJson() + if (existingRaw !== undefined) { + const rawString = Buffer.isBuffer(existingRaw) + ? existingRaw.toString('utf8') + : existingRaw + try { + const decoded = Buffer.from(rawString, 'base64').toString('utf8') + editor.fromJSON(decoded) + } catch { + // If decoding fails, start fresh. + } + } else { + // Initialize empty editor for new file. + editor.create(configFilePath) + } + // Update with the captured config state. + editor.update(configToSave) + // Get content with formatting symbols stripped. + const contentToSave = Object.fromEntries( + Object.entries(editor.content).filter( + ([key]) => typeof key === 'string', + ), + ) + const jsonContent = JSON.stringify(contentToSave) writeFileSync( - socketAppDataPath, - Buffer.from(JSON.stringify(localConfig)).toString('base64'), + configFilePath, + Buffer.from(jsonContent).toString('base64'), ) } }) diff --git a/src/utils/config.test.mts b/src/utils/config.test.mts index 2df4dad75..f88d42ca5 100644 --- a/src/utils/config.test.mts +++ b/src/utils/config.test.mts @@ -1,4 +1,10 @@ -import { promises as fs, mkdtempSync } from 'node:fs' +import { + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, + promises as fs, +} from 'node:fs' import os from 'node:os' import path from 'node:path' @@ -6,11 +12,15 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { findSocketYmlSync, + getConfigValue, overrideCachedConfig, + resetConfigForTesting, updateConfigValue, } from './config.mts' import { testPath } from '../../test/utils.mts' +import type { LocalConfig } from './config.mts' + const fixtureBaseDir = path.join(testPath, 'fixtures/utils/config') describe('utils/config', () => { @@ -80,7 +90,7 @@ describe('utils/config', () => { expect(result.data).toBe(undefined) } finally { // Clean up the temporary directory. - await fs.rm(tmpDir, { force: true, recursive: true }) + rmSync(tmpDir, { force: true, recursive: true }) } }) }) diff --git a/src/utils/editable-json.mts b/src/utils/editable-json.mts new file mode 100644 index 000000000..575793793 --- /dev/null +++ b/src/utils/editable-json.mts @@ -0,0 +1,381 @@ +/** + * @fileoverview EditableJson utility for non-destructive JSON file manipulation. + * Preserves formatting (indentation and line endings) when updating JSON files. + * This is a standalone implementation copied from @socketsecurity/lib/json/edit. + */ + +import { promises as fs } from 'node:fs' +import { setTimeout } from 'node:timers/promises' +import { isDeepStrictEqual } from 'node:util' + +// Symbols used to store formatting metadata in JSON objects. +const INDENT_SYMBOL = Symbol.for('indent') +const NEWLINE_SYMBOL = Symbol.for('newline') + +/** + * Formatting metadata for JSON files. + */ +interface JsonFormatting { + indent: string | number + newline: string +} + +/** + * Options for saving editable JSON files. + */ +interface EditableJsonSaveOptions { + /** + * Whether to ignore whitespace-only changes when determining if save is needed. + * @default false + */ + ignoreWhitespace?: boolean | undefined + /** + * Whether to sort object keys alphabetically before saving. + * @default false + */ + sort?: boolean | undefined +} + +/** + * Detect indentation from a JSON string. + * Supports space-based indentation (returns count) or mixed indentation (returns string). + */ +function detectIndent(json: string): string | number { + const match = json.match(/^[{[][\r\n]+(\s+)/m) + if (!match) { + return 2 + } + const indent = match[1] + if (/^ +$/.test(indent)) { + return indent.length + } + return indent +} + +/** + * Detect newline character(s) from a JSON string. + * Supports LF (\n) and CRLF (\r\n) line endings. + */ +function detectNewline(json: string): string { + const match = json.match(/\r?\n/) + return match ? match[0] : '\n' +} + +/** + * Get default formatting for JSON files. + */ +function getDefaultFormatting(): JsonFormatting { + return { + indent: 2, + newline: '\n', + } +} + +/** + * Sort object keys alphabetically. + * Creates a new object with sorted keys (does not mutate input). + */ +function sortKeys(obj: Record): Record { + const sorted: Record = { __proto__: null } as Record< + string, + unknown + > + const keys = Object.keys(obj).sort() + for (const key of keys) { + sorted[key] = obj[key] + } + return sorted +} + +/** + * Stringify JSON with specific formatting. + * Applies indentation and line ending preferences. + */ +function stringifyWithFormatting( + content: Record, + formatting: JsonFormatting, +): string { + const { indent, newline } = formatting + const format = indent === undefined || indent === null ? ' ' : indent + const eol = newline === undefined || newline === null ? '\n' : newline + return `${JSON.stringify(content, undefined, format)}\n`.replace(/\n/g, eol) +} + +/** + * Strip formatting symbols from content object. + * Removes Symbol.for('indent') and Symbol.for('newline') from the object. + */ +function stripFormattingSymbols( + content: Record, +): Record { + const { + [INDENT_SYMBOL]: _indent, + [NEWLINE_SYMBOL]: _newline, + ...rest + } = content + return rest as Record +} + +/** + * Extract formatting from content object that has symbol-based metadata. + */ +function getFormattingFromContent( + content: Record, +): JsonFormatting { + const indent = content[INDENT_SYMBOL] + const newline = content[NEWLINE_SYMBOL] + return { + indent: + indent === undefined || indent === null ? 2 : (indent as string | number), + newline: + newline === undefined || newline === null ? '\n' : (newline as string), + } +} + +/** + * Determine if content should be saved based on changes and options. + */ +function shouldSave( + currentContent: Record, + originalContent: Record | undefined, + originalFileContent: string, + options: EditableJsonSaveOptions = {}, +): boolean { + const { ignoreWhitespace = false, sort = false } = options + const content = stripFormattingSymbols(currentContent) + const sortedContent = sort ? sortKeys(content) : content + const origContent = originalContent + ? stripFormattingSymbols(originalContent) + : {} + + if (ignoreWhitespace) { + return !isDeepStrictEqual(sortedContent, origContent) + } + + const formatting = getFormattingFromContent(currentContent) + const newFileContent = stringifyWithFormatting(sortedContent, formatting) + return newFileContent.trim() !== originalFileContent.trim() +} + +/** + * Retry write operation with exponential backoff for file system issues. + */ +async function retryWrite( + filepath: string, + content: string, + retries = 3, + baseDelay = 10, +): Promise { + for (let attempt = 0; attempt <= retries; attempt++) { + try { + await fs.writeFile(filepath, content) + if (process.platform === 'win32') { + await setTimeout(50) + let accessRetries = 0 + const maxAccessRetries = 5 + // eslint-disable-next-line no-await-in-loop + while (accessRetries < maxAccessRetries) { + try { + // eslint-disable-next-line no-await-in-loop + await fs.access(filepath) + // eslint-disable-next-line no-await-in-loop + await setTimeout(10) + break + } catch { + const delay = 20 * (accessRetries + 1) + // eslint-disable-next-line no-await-in-loop + await setTimeout(delay) + accessRetries++ + } + } + } + return + } catch (err) { + const isLastAttempt = attempt === retries + const isRetriableError = + err instanceof Error && + 'code' in err && + (err.code === 'EPERM' || err.code === 'EBUSY' || err.code === 'ENOENT') + if (!isRetriableError || isLastAttempt) { + throw err + } + const delay = baseDelay * 2 ** attempt + // eslint-disable-next-line no-await-in-loop + await setTimeout(delay) + } + } +} + +/** + * Parse JSON string. + */ +function parseJson(content: string): Record { + return JSON.parse(content) as Record +} + +/** + * Read file with retry logic for file system issues. + */ +async function readFile(filepath: string): Promise { + const maxRetries = process.platform === 'win32' ? 5 : 1 + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + return await fs.readFile(filepath, 'utf8') + } catch (err) { + const isLastAttempt = attempt === maxRetries + const isEnoent = + err instanceof Error && 'code' in err && err.code === 'ENOENT' + if (!isEnoent || isLastAttempt) { + throw err + } + const delay = process.platform === 'win32' ? 50 * (attempt + 1) : 20 + // eslint-disable-next-line no-await-in-loop + await setTimeout(delay) + } + } + throw new Error('Unreachable code') +} + +/** + * EditableJson class for non-destructive JSON file manipulation. + * Preserves formatting when updating JSON files. + */ +export class EditableJson> { + private _canSave = true + private _content: Record = {} + private _path: string | undefined = undefined + private _readFileContent = '' + private _readFileJson: Record | undefined = undefined + + get content(): Readonly { + return this._content as Readonly + } + + get filename(): string { + const path = this._path + if (!path) { + return '' + } + return path + } + + get path(): string | undefined { + return this._path + } + + /** + * Create a new JSON file instance. + */ + create(path: string): this { + this._path = path + this._content = {} + this._canSave = true + return this + } + + /** + * Initialize from content object (disables saving). + */ + fromContent(data: unknown): this { + this._content = data as Record + this._canSave = false + return this + } + + /** + * Initialize from JSON string. + */ + fromJSON(data: string): this { + const parsed = parseJson(data) + const indent = detectIndent(data) + const newline = detectNewline(data) + parsed[INDENT_SYMBOL] = indent + parsed[NEWLINE_SYMBOL] = newline + this._content = parsed as Record + return this + } + + /** + * Load JSON file from disk. + */ + async load(path: string, create?: boolean): Promise { + this._path = path + let parseErr: Error | undefined + try { + this._readFileContent = await readFile(this.filename) + } catch (err) { + if (!create) { + throw err + } + parseErr = err as Error + } + if (parseErr) { + throw parseErr + } + this.fromJSON(this._readFileContent) + this._readFileJson = parseJson(this._readFileContent) + return this + } + + /** + * Update content with new values. + */ + update(content: Partial): this { + this._content = { + ...this._content, + ...content, + } + return this + } + + /** + * Save JSON file to disk asynchronously. + */ + async save(options?: EditableJsonSaveOptions): Promise { + if (!this._canSave || this.content === undefined) { + throw new Error('No file path to save to') + } + if ( + !shouldSave( + this._content, + this._readFileJson as Record | undefined, + this._readFileContent, + options, + ) + ) { + return false + } + const content = stripFormattingSymbols(this._content) + const sortedContent = options?.sort ? sortKeys(content) : content + const formatting = getFormattingFromContent(this._content) + const fileContent = stringifyWithFormatting(sortedContent, formatting) + await retryWrite(this.filename, fileContent) + this._readFileContent = fileContent + this._readFileJson = parseJson(fileContent) + return true + } + + /** + * Check if save will occur based on current changes. + */ + willSave(options?: EditableJsonSaveOptions): boolean { + if (!this._canSave || this.content === undefined) { + return false + } + return shouldSave( + this._content, + this._readFileJson as Record | undefined, + this._readFileContent, + options, + ) + } +} + +/** + * Get the EditableJson class for JSON file manipulation. + */ +export function getEditableJsonClass< + T = Record, +>(): typeof EditableJson { + return EditableJson as typeof EditableJson +} From ffad1d4c148d39c1824d43d7ebcc0ac3f2ec37a1 Mon Sep 17 00:00:00 2001 From: jdalton Date: Mon, 22 Dec 2025 13:24:26 -0500 Subject: [PATCH 2/5] fix: address PR review feedback - Preserve JSON formatting by using editor's indent/newline symbols - Handle deleted config keys explicitly (editor.update only merges) - Capture config snapshot at write time, not schedule time - Fix load method create parameter to actually create empty state --- src/utils/config.mts | 49 +++++++++++++++++++++++++++++-------- src/utils/editable-json.mts | 14 +++++------ 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/utils/config.mts b/src/utils/config.mts index 9ff3b8063..dc086327f 100644 --- a/src/utils/config.mts +++ b/src/utils/config.mts @@ -350,10 +350,11 @@ export function updateConfigValue( if (!_pendingSave) { _pendingSave = true - // Capture the current config state to save. - const configToSave = { ...localConfig } process.nextTick(() => { _pendingSave = false + // Capture the config state at write time, not at schedule time. + // This ensures all updates in the same tick are included. + const configToSave = { ...localConfig } const { socketAppDataPath } = constants if (socketAppDataPath) { mkdirSync(socketAppDataPath, { recursive: true }) @@ -377,17 +378,45 @@ export function updateConfigValue( editor.create(configFilePath) } // Update with the captured config state. - editor.update(configToSave) - // Get content with formatting symbols stripped. - const contentToSave = Object.fromEntries( - Object.entries(editor.content).filter( - ([key]) => typeof key === 'string', - ), + // Note: We need to handle deletions explicitly since editor.update() only merges. + // First, get all keys from the existing content. + const existingKeys = new Set( + Object.keys(editor.content).filter(k => typeof k === 'string') ) - const jsonContent = JSON.stringify(contentToSave) + const newKeys = new Set(Object.keys(configToSave)) + + // Delete keys that are in existing but not in new config. + for (const key of existingKeys) { + if (!newKeys.has(key)) { + delete (editor.content as any)[key] + } + } + + // Now update with new values. + editor.update(configToSave) + // Use the editor's internal stringify which preserves formatting. + // We need to extract the content without symbols and then stringify + // with the formatting metadata. + const { getEditableJsonClass: _, ...contentWithoutImport } = editor.content as any + const INDENT_SYMBOL = Symbol.for('indent') + const NEWLINE_SYMBOL = Symbol.for('newline') + const indent = (editor.content as any)[INDENT_SYMBOL] ?? 2 + const newline = (editor.content as any)[NEWLINE_SYMBOL] ?? '\n' + + // Strip formatting symbols from content. + const contentToSave: Record = {} + for (const [key, val] of Object.entries(editor.content)) { + if (typeof key === 'string') { + contentToSave[key] = val + } + } + + // Stringify with formatting preserved. + const jsonContent = JSON.stringify(contentToSave, undefined, indent) + .replace(/\n/g, newline) writeFileSync( configFilePath, - Buffer.from(jsonContent).toString('base64'), + Buffer.from(jsonContent + newline).toString('base64'), ) } }) diff --git a/src/utils/editable-json.mts b/src/utils/editable-json.mts index 575793793..45afa7796 100644 --- a/src/utils/editable-json.mts +++ b/src/utils/editable-json.mts @@ -300,20 +300,20 @@ export class EditableJson> { */ async load(path: string, create?: boolean): Promise { this._path = path - let parseErr: Error | undefined try { this._readFileContent = await readFile(this.filename) + this.fromJSON(this._readFileContent) + this._readFileJson = parseJson(this._readFileContent) } catch (err) { if (!create) { throw err } - parseErr = err as Error + // File doesn't exist and create is true - initialize empty. + this._content = {} + this._readFileContent = '' + this._readFileJson = undefined + this._canSave = true } - if (parseErr) { - throw parseErr - } - this.fromJSON(this._readFileContent) - this._readFileJson = parseJson(this._readFileContent) return this } From b3b4bf4317e816b97c6c8911cbe1e81a0031a1a2 Mon Sep 17 00:00:00 2001 From: jdalton Date: Mon, 22 Dec 2025 13:28:28 -0500 Subject: [PATCH 3/5] fix: TypeScript compilation errors - Fix symbol index type errors with type assertions - Remove unused getDefaultFormatting function - Add null check for match[1] in detectIndent - Remove unused contentWithoutImport variable --- src/utils/config.mts | 4 +--- src/utils/editable-json.mts | 16 ++++------------ 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/utils/config.mts b/src/utils/config.mts index dc086327f..3cb7d7658 100644 --- a/src/utils/config.mts +++ b/src/utils/config.mts @@ -395,9 +395,7 @@ export function updateConfigValue( // Now update with new values. editor.update(configToSave) // Use the editor's internal stringify which preserves formatting. - // We need to extract the content without symbols and then stringify - // with the formatting metadata. - const { getEditableJsonClass: _, ...contentWithoutImport } = editor.content as any + // Extract the formatting symbols from the content. const INDENT_SYMBOL = Symbol.for('indent') const NEWLINE_SYMBOL = Symbol.for('newline') const indent = (editor.content as any)[INDENT_SYMBOL] ?? 2 diff --git a/src/utils/editable-json.mts b/src/utils/editable-json.mts index 45afa7796..d0c3b3c54 100644 --- a/src/utils/editable-json.mts +++ b/src/utils/editable-json.mts @@ -42,7 +42,7 @@ interface EditableJsonSaveOptions { */ function detectIndent(json: string): string | number { const match = json.match(/^[{[][\r\n]+(\s+)/m) - if (!match) { + if (!match || !match[1]) { return 2 } const indent = match[1] @@ -61,15 +61,6 @@ function detectNewline(json: string): string { return match ? match[0] : '\n' } -/** - * Get default formatting for JSON files. - */ -function getDefaultFormatting(): JsonFormatting { - return { - indent: 2, - newline: '\n', - } -} /** * Sort object keys alphabetically. @@ -289,8 +280,9 @@ export class EditableJson> { const parsed = parseJson(data) const indent = detectIndent(data) const newline = detectNewline(data) - parsed[INDENT_SYMBOL] = indent - parsed[NEWLINE_SYMBOL] = newline + // Use type assertion to allow symbol indexing. + ;(parsed as any)[INDENT_SYMBOL] = indent + ;(parsed as any)[NEWLINE_SYMBOL] = newline this._content = parsed as Record return this } From ed75ea2f855cb177d561aab59a3606a59906d2d7 Mon Sep 17 00:00:00 2001 From: jdalton Date: Mon, 22 Dec 2025 13:52:02 -0500 Subject: [PATCH 4/5] fix: ESLint errors in config and editable-json - Fix import sort order in config.test.mts (promises as fs sorted by alias name) - Add no-await-in-loop eslint-disable comments for retry loops in editable-json.mts - Remove unused eslint-disable directive --- src/utils/config.mts | 9 ++++++--- src/utils/config.test.mts | 2 +- src/utils/editable-json.mts | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/utils/config.mts b/src/utils/config.mts index 3cb7d7658..a5dd0d2b6 100644 --- a/src/utils/config.mts +++ b/src/utils/config.mts @@ -381,7 +381,7 @@ export function updateConfigValue( // Note: We need to handle deletions explicitly since editor.update() only merges. // First, get all keys from the existing content. const existingKeys = new Set( - Object.keys(editor.content).filter(k => typeof k === 'string') + Object.keys(editor.content).filter(k => typeof k === 'string'), ) const newKeys = new Set(Object.keys(configToSave)) @@ -410,8 +410,11 @@ export function updateConfigValue( } // Stringify with formatting preserved. - const jsonContent = JSON.stringify(contentToSave, undefined, indent) - .replace(/\n/g, newline) + const jsonContent = JSON.stringify( + contentToSave, + undefined, + indent, + ).replace(/\n/g, newline) writeFileSync( configFilePath, Buffer.from(jsonContent + newline).toString('base64'), diff --git a/src/utils/config.test.mts b/src/utils/config.test.mts index f88d42ca5..e9a171a47 100644 --- a/src/utils/config.test.mts +++ b/src/utils/config.test.mts @@ -1,9 +1,9 @@ import { + promises as fs, mkdtempSync, readFileSync, rmSync, writeFileSync, - promises as fs, } from 'node:fs' import os from 'node:os' import path from 'node:path' diff --git a/src/utils/editable-json.mts b/src/utils/editable-json.mts index d0c3b3c54..680876e1a 100644 --- a/src/utils/editable-json.mts +++ b/src/utils/editable-json.mts @@ -61,7 +61,6 @@ function detectNewline(json: string): string { return match ? match[0] : '\n' } - /** * Sort object keys alphabetically. * Creates a new object with sorted keys (does not mutate input). @@ -159,12 +158,13 @@ async function retryWrite( ): Promise { for (let attempt = 0; attempt <= retries; attempt++) { try { + // eslint-disable-next-line no-await-in-loop await fs.writeFile(filepath, content) if (process.platform === 'win32') { + // eslint-disable-next-line no-await-in-loop await setTimeout(50) let accessRetries = 0 const maxAccessRetries = 5 - // eslint-disable-next-line no-await-in-loop while (accessRetries < maxAccessRetries) { try { // eslint-disable-next-line no-await-in-loop @@ -211,6 +211,7 @@ async function readFile(filepath: string): Promise { const maxRetries = process.platform === 'win32' ? 5 : 1 for (let attempt = 0; attempt <= maxRetries; attempt++) { try { + // eslint-disable-next-line no-await-in-loop return await fs.readFile(filepath, 'utf8') } catch (err) { const isLastAttempt = attempt === maxRetries From 61a8b31c105555bbba3aaefc2444836d82df13e8 Mon Sep 17 00:00:00 2001 From: jdalton Date: Tue, 23 Dec 2025 08:59:10 -0500 Subject: [PATCH 5/5] chore: remove unused imports from config.test.mts --- src/utils/config.test.mts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/utils/config.test.mts b/src/utils/config.test.mts index e9a171a47..d68308e67 100644 --- a/src/utils/config.test.mts +++ b/src/utils/config.test.mts @@ -8,19 +8,15 @@ import { import os from 'node:os' import path from 'node:path' -import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { beforeEach, describe, expect, it } from 'vitest' import { findSocketYmlSync, - getConfigValue, overrideCachedConfig, - resetConfigForTesting, updateConfigValue, } from './config.mts' import { testPath } from '../../test/utils.mts' -import type { LocalConfig } from './config.mts' - const fixtureBaseDir = path.join(testPath, 'fixtures/utils/config') describe('utils/config', () => {