From de2314c6222d3731550e039d5fff57a783217192 Mon Sep 17 00:00:00 2001 From: Arthur Tkachenko Date: Tue, 29 Sep 2026 06:13:58 +0300 Subject: [PATCH 1/2] feat: DEFAULT_MESSAGES as single source of truth for error codes Closes #262 --- src/constants.ts | 7 +------ src/errors.ts | 16 +++++++++++++++- src/index.ts | 2 +- src/write.ts | 19 +++---------------- tests/default-messages.test.ts | 22 ++++++++++++++++++++++ tests/index.test.ts | 6 +++--- 6 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 tests/default-messages.test.ts diff --git a/src/constants.ts b/src/constants.ts index 0e0e67e..8cb43b9 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,8 +1,3 @@ -export const ERROR_NO_CONTENT = 'content variable is empty'; -export const ERROR_TYPE_NOT_STRING = 'content variable is not a string'; - +/** Status strings that are not MarkupGeneratorError codes. */ export const HTML_EMAIL_SUCCESS = 'Content has correct html!!!'; export const HTML_EMAIL_ERROR = 'Content has not correct html!!!'; - -export const CONST_FILE_CREATED = ''; -export const CONST_FILE_NOT_WRITTEN = 'file not written'; diff --git a/src/errors.ts b/src/errors.ts index c40f4b3..f6e1047 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -8,10 +8,24 @@ export type MarkupGeneratorErrorCode = | 'EIMPORT' | 'EREAD'; +export const DEFAULT_MESSAGES: Record = { + EMPTY_CONTENT: 'content variable is empty', + NOT_A_STRING: 'content variable is not a string', + FILE_EXISTS: 'file already exists', + WRITE_FAILED: 'file not written', + JSON_READ: 'failed to read JSON file', + JSON_PARSE: 'failed to parse JSON file', + EIMPORT: 'failed to import module', + EREAD: 'failed to read path', +}; + export class MarkupGeneratorError extends Error { readonly code: MarkupGeneratorErrorCode; - constructor(code: MarkupGeneratorErrorCode, message: string) { + constructor( + code: MarkupGeneratorErrorCode, + message: string = DEFAULT_MESSAGES[code] + ) { super(message); this.name = 'MarkupGeneratorError'; this.code = code; diff --git a/src/index.ts b/src/index.ts index cf26960..94d4c37 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,7 +40,7 @@ export type { RenderOptions, WriteOptions, } from './templateGenerator'; -export { MarkupGeneratorError } from './errors'; +export { MarkupGeneratorError, DEFAULT_MESSAGES } from './errors'; export type { MarkupGeneratorErrorCode } from './errors'; export { diff --git a/src/write.ts b/src/write.ts index 4bc7a05..bb116c6 100644 --- a/src/write.ts +++ b/src/write.ts @@ -6,16 +6,6 @@ import { MarkupGeneratorError } from './errors'; import { generateFileName } from './generateFileName'; import type { WriteGeneratedFileOptions } from './types'; -/** - * Writes UTF-8 text to `{cwd}/{dir}/{fileName}`. - * - * Contract: - * - `dir` is resolved from `process.cwd()` - * - missing directories are created - * - existing files are overwritten unless a caller uses writeGeneratedFile({ overwrite: 'error' }) - * - empty content throws EMPTY_CONTENT - * - non-string content throws NOT_A_STRING - */ const writeHTML = async ( fileName: string, data: string, @@ -23,14 +13,11 @@ const writeHTML = async ( message: string = '' ): Promise => { if (!data) { - throw new MarkupGeneratorError('EMPTY_CONTENT', 'content variable is empty'); + throw new MarkupGeneratorError('EMPTY_CONTENT'); } if (typeof data !== 'string') { - throw new MarkupGeneratorError( - 'NOT_A_STRING', - 'content variable is not a string' - ); + throw new MarkupGeneratorError('NOT_A_STRING'); } if (dir === '') { @@ -46,7 +33,7 @@ const writeHTML = async ( console.log(message); } } catch { - throw new MarkupGeneratorError('WRITE_FAILED', 'file not written'); + throw new MarkupGeneratorError('WRITE_FAILED'); } }; diff --git a/tests/default-messages.test.ts b/tests/default-messages.test.ts new file mode 100644 index 0000000..2298b92 --- /dev/null +++ b/tests/default-messages.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from 'vitest'; +import { DEFAULT_MESSAGES, MarkupGeneratorError } from '../src/index'; + +describe('DEFAULT_MESSAGES', () => { + test('every code has a default message', () => { + expect(new MarkupGeneratorError('EMPTY_CONTENT').message).toBe( + DEFAULT_MESSAGES.EMPTY_CONTENT + ); + expect(new MarkupGeneratorError('WRITE_FAILED').message).toBe( + DEFAULT_MESSAGES.WRITE_FAILED + ); + expect(new MarkupGeneratorError('FILE_EXISTS').message).toBe( + DEFAULT_MESSAGES.FILE_EXISTS + ); + }); + + test('explicit message still wins', () => { + expect(new MarkupGeneratorError('FILE_EXISTS', 'custom').message).toBe( + 'custom' + ); + }); +}); diff --git a/tests/index.test.ts b/tests/index.test.ts index 505851c..1ee8d53 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -7,8 +7,8 @@ import { readSourceFile, writeHTML, writingFile, + DEFAULT_MESSAGES, } from '../src/index'; -import { ERROR_NO_CONTENT, ERROR_TYPE_NOT_STRING } from '../src/constants'; const UUID_RE = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'; @@ -50,14 +50,14 @@ describe('writeHTML', () => { test('throws when content is empty', async () => { await expect(writeHTML('empty.html', '', outDir)).rejects.toThrow( - ERROR_NO_CONTENT + DEFAULT_MESSAGES.EMPTY_CONTENT ); }); test('throws when content is not a string', async () => { await expect( writeHTML('bad.html', 123 as unknown as string, outDir) - ).rejects.toThrow(ERROR_TYPE_NOT_STRING); + ).rejects.toThrow(DEFAULT_MESSAGES.NOT_A_STRING); }); }); From a59087c3170d7fdeca7c90cc32e87e8fd627f220 Mon Sep 17 00:00:00 2001 From: Arthur Tkachenko Date: Tue, 29 Sep 2026 06:15:45 +0300 Subject: [PATCH 2/2] docs: restore writeHTML contract JSDoc --- src/write.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/write.ts b/src/write.ts index bb116c6..15d0881 100644 --- a/src/write.ts +++ b/src/write.ts @@ -6,6 +6,17 @@ import { MarkupGeneratorError } from './errors'; import { generateFileName } from './generateFileName'; import type { WriteGeneratedFileOptions } from './types'; +/** + * Writes UTF-8 text to `{cwd}/{dir}/{fileName}`. + * + * Contract: + * - `dir` is resolved from `process.cwd()` + * - missing directories are created + * - existing files are overwritten unless a caller uses writeGeneratedFile({ overwrite: 'error' }) + * - empty content throws EMPTY_CONTENT + * - non-string content throws NOT_A_STRING + * - write failures throw WRITE_FAILED (default message from DEFAULT_MESSAGES) + */ const writeHTML = async ( fileName: string, data: string, @@ -50,6 +61,10 @@ const writingFile = async (content: string, name: string = 'prefix'): Promise => {