diff --git a/src/cli.ts b/src/cli.ts index 58b147c..1206628 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -3,8 +3,16 @@ import { red } from 'colorette'; import { program } from 'commander'; import type { Issue } from 'cspell'; import { lint } from 'cspell'; -import { findConfig } from './config'; -import { reportErrors, reportSuccess, resetDisplay, showProgress, showStartupMessage, stopSpinner } from './display'; +import { findOrCreateConfig } from './config'; +import { + reportErrors, + reportSuccess, + resetDisplay, + showConfigurationFilePath, + showProgress, + showStartupMessage, + stopSpinner +} from './display'; import { handleIssues } from './handleIssue'; interface CLIOptions { @@ -52,7 +60,8 @@ const globs = program.processedArgs[0]; showStartupMessage(globs); const start = async () => { - const configPath = await findConfig(options.config); + const configPath = await findOrCreateConfig(options.config); + showConfigurationFilePath(configPath); const { issues: issueCount, diff --git a/src/config.ts b/src/config.ts index 894b6a0..5a0fd5a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,18 +1,24 @@ import { extname } from 'node:path'; +import { existsSync } from 'node:fs'; import { readFile, writeFile } from 'node:fs/promises'; import type { CSpellSettings } from 'cspell'; import { searchForConfig } from 'cspell-lib'; +import findDefaultConfigPath from 'application-config-path'; // eslint-disable-next-line import/no-relative-packages import { previousState } from './shared'; let configPath: string | undefined; -export const findConfig = async (config?: string) => { +export const writeToSettings = async (settings: CSpellSettings | { cspell: CSpellSettings }) => { + await writeFile(configPath!, JSON.stringify(settings, null, 4)); +}; + +export const findOrCreateConfig = async (config?: string) => { // Try to locate a config file in the current working directory, or with the `config` option if it was provided. const configSource = config ?? (await searchForConfig(process.cwd()))?.__importRef?.filename; // If no config file was found, use/create a config file in the user's configuration directory (platform dependent). - const path = configSource ?? (await import('application-config-path')).default('cspell.json'); + const path = configSource ?? findDefaultConfigPath('cspell.json'); // Only JSON files are supported to prevent more dependencies for yml parsing. If the config file is not a JSON // file, it can still be used, but it won't be updated with new ignored words. @@ -20,6 +26,11 @@ export const findConfig = async (config?: string) => { configPath = path; } + // If configSource is undefined, check if default config path exists. If it doesn't, create it. + if (!configSource && !existsSync(path)) { + await writeToSettings({}); + } + return path; }; @@ -37,10 +48,6 @@ export const getSettings = async (): Promise { - await writeFile(configPath!, JSON.stringify(settings, null, 4)); -}; - export const addIgnoreWordToSettings = async (word: string) => { const settings = await getSettings(); if (!settings) { diff --git a/src/display.ts b/src/display.ts index b14b4af..a332a79 100644 --- a/src/display.ts +++ b/src/display.ts @@ -132,6 +132,10 @@ export const showStartupMessage = (globs: string[]) => { console.log(`\nFinding files matching ${cyan(globs.join(', '))}`); }; +export const showConfigurationFilePath = (path: string) => { + console.log(`Using configuration from ${cyan(path)}\n`); +}; + let spinner: Spinner | undefined; export const stopSpinner = () => { spinner?.stop(); diff --git a/test/index.test.ts b/test/index.test.ts index 5c1f35d..f556a8a 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,7 +1,10 @@ +import fs from 'node:fs'; +import { join } from 'node:path'; import { writeFile } from 'node:fs/promises'; import { vi, afterEach, describe, test, expect } from 'vitest'; import { handleIssues } from '../src/handleIssue'; import { determineAction, formatContext } from '../src/display'; +import { findOrCreateConfig } from '../src/config'; import { Action } from '../src/shared'; import { allTypoSets } from './fixtures/data'; import { sampleReplacer } from './mocks/issue'; @@ -55,3 +58,19 @@ describe.each(allTypoSets)('%s', (name, data) => { expect(contextDisplays, name).toMatchObject(data.displays); }); }); + +describe('findConfig', () => { + test('should find a mock config file in working directory', async () => { + const configPath = join(process.cwd(), 'cspell.json'); + + const readFileSpy = vi.spyOn(fs, 'readFile'); + + readFileSpy.mockImplementation(((path: string, _encoding: any, callback: (err: null, data: string) => void) => { + callback(null, path === configPath ? '{}' : ''); + }) as typeof fs.readFile); + + const config = await findOrCreateConfig(); + + expect(config).toBe(configPath); + }); +});