diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2d55cc4..7203d7b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -5,5 +5,4 @@ export * from './lib/plugin-loader'; export * from './lib/base-generator'; export * from './lib/auto-installer'; export * from './lib/type-guards'; -export * from './lib/logger'; export * from './lib/validation'; diff --git a/packages/core/src/lib/logger.spec.ts b/packages/core/src/lib/logger.spec.ts deleted file mode 100644 index a81fee0..0000000 --- a/packages/core/src/lib/logger.spec.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { Logger, LogLevel } from './logger'; - -describe('Logger', () => { - let logger: Logger; - let consoleDebugSpy: jest.SpyInstance; - let consoleInfoSpy: jest.SpyInstance; - let consoleWarnSpy: jest.SpyInstance; - let consoleErrorSpy: jest.SpyInstance; - - beforeEach(() => { - Logger.reset(); - consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation(); - consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation(); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); - consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); - }); - - afterEach(() => { - jest.restoreAllMocks(); - delete process.env['NX_PLUGIN_LOG_LEVEL']; - }); - - describe('getInstance', () => { - it('should return singleton instance', () => { - const instance1 = Logger.getInstance(); - const instance2 = Logger.getInstance(); - expect(instance1).toBe(instance2); - }); - - it('should create new instance after reset', () => { - const instance1 = Logger.getInstance(); - Logger.reset(); - const instance2 = Logger.getInstance(); - expect(instance1).not.toBe(instance2); - }); - }); - - describe('constructor', () => { - it('should use default options', () => { - logger = new Logger(); - expect(logger.getLevel()).toBe('info'); - }); - - it('should use provided options', () => { - logger = new Logger({ level: 'debug' }); - expect(logger.getLevel()).toBe('debug'); - }); - - it('should use environment variable for log level', () => { - process.env['NX_PLUGIN_LOG_LEVEL'] = 'debug'; - logger = new Logger(); - expect(logger.getLevel()).toBe('debug'); - }); - - it('should prefer explicit option over environment variable', () => { - process.env['NX_PLUGIN_LOG_LEVEL'] = 'debug'; - logger = new Logger({ level: 'error' }); - expect(logger.getLevel()).toBe('error'); - }); - }); - - describe('log levels', () => { - beforeEach(() => { - logger = new Logger({ timestamp: false, prefix: '' }); - }); - - it('should log debug messages when level is debug', () => { - logger.setLevel('debug'); - logger.debug('debug message'); - expect(consoleDebugSpy).toHaveBeenCalledWith('[DEBUG] debug message'); - }); - - it('should not log debug messages when level is info', () => { - logger.setLevel('info'); - logger.debug('debug message'); - expect(consoleDebugSpy).not.toHaveBeenCalled(); - }); - - it('should log info messages when level is info', () => { - logger.setLevel('info'); - logger.info('info message'); - expect(consoleInfoSpy).toHaveBeenCalledWith('[INFO] info message'); - }); - - it('should not log info messages when level is warn', () => { - logger.setLevel('warn'); - logger.info('info message'); - expect(consoleInfoSpy).not.toHaveBeenCalled(); - }); - - it('should log warn messages when level is warn', () => { - logger.setLevel('warn'); - logger.warn('warning message'); - expect(consoleWarnSpy).toHaveBeenCalledWith('[WARN] warning message'); - }); - - it('should not log warn messages when level is error', () => { - logger.setLevel('error'); - logger.warn('warning message'); - expect(consoleWarnSpy).not.toHaveBeenCalled(); - }); - - it('should always log error messages', () => { - const levels: LogLevel[] = ['debug', 'info', 'warn', 'error']; - for (const level of levels) { - logger.setLevel(level); - logger.error('error message'); - expect(consoleErrorSpy).toHaveBeenCalled(); - consoleErrorSpy.mockClear(); - } - }); - }); - - describe('message formatting', () => { - it('should include prefix when provided', () => { - logger = new Logger({ prefix: '[test]', timestamp: false }); - logger.info('message'); - expect(consoleInfoSpy).toHaveBeenCalledWith('[test] [INFO] message'); - }); - - it('should include timestamp when enabled', () => { - const dateSpy = jest.spyOn(Date.prototype, 'toISOString').mockReturnValue('2024-01-01T00:00:00.000Z'); - logger = new Logger({ timestamp: true, prefix: '' }); - logger.info('message'); - expect(consoleInfoSpy).toHaveBeenCalledWith('2024-01-01T00:00:00.000Z [INFO] message'); - dateSpy.mockRestore(); - }); - - it('should format additional arguments', () => { - logger = new Logger({ timestamp: false, prefix: '' }); - logger.info('message', 'arg1', 123); - expect(consoleInfoSpy).toHaveBeenCalledWith('[INFO] message arg1 123'); - }); - - it('should format objects as JSON', () => { - logger = new Logger({ timestamp: false, prefix: '' }); - const obj = { key: 'value' }; - logger.info('message', obj); - expect(consoleInfoSpy).toHaveBeenCalledWith('[INFO] message {\n "key": "value"\n}'); - }); - - it('should format errors with stack trace', () => { - logger = new Logger({ timestamp: false, prefix: '' }); - const error = new Error('test error'); - error.stack = 'Error: test error\n at test.js:1:1'; - logger.error('An error occurred:', error); - expect(consoleErrorSpy).toHaveBeenCalledWith( - '[ERROR] An error occurred: Error: test error\nError: test error\n at test.js:1:1' - ); - }); - - it('should handle circular references in objects', () => { - logger = new Logger({ timestamp: false, prefix: '' }); - const obj: Record = { key: 'value' }; - obj.circular = obj; - logger.info('message', obj); - const call = consoleInfoSpy.mock.calls[0][0]; - expect(call).toContain('[INFO] message'); - expect(call).toContain('[object Object]'); - }); - }); - - describe('utility methods', () => { - beforeEach(() => { - logger = new Logger({ level: 'debug', timestamp: false, prefix: '[test]' }); - }); - - it('should support console.group', () => { - const groupSpy = jest.spyOn(console, 'group').mockImplementation(); - logger.group('Group Label'); - expect(groupSpy).toHaveBeenCalledWith('[test] [DEBUG] Group Label'); - groupSpy.mockRestore(); - }); - - it('should support console.groupEnd', () => { - const groupEndSpy = jest.spyOn(console, 'groupEnd').mockImplementation(); - logger.groupEnd(); - expect(groupEndSpy).toHaveBeenCalled(); - groupEndSpy.mockRestore(); - }); - - it('should support console.time', () => { - const timeSpy = jest.spyOn(console, 'time').mockImplementation(); - logger.time('Timer'); - expect(timeSpy).toHaveBeenCalledWith('[test] Timer'); - timeSpy.mockRestore(); - }); - - it('should support console.timeEnd', () => { - const timeEndSpy = jest.spyOn(console, 'timeEnd').mockImplementation(); - logger.timeEnd('Timer'); - expect(timeEndSpy).toHaveBeenCalledWith('[test] Timer'); - timeEndSpy.mockRestore(); - }); - - it('should not call utility methods when level is too high', () => { - logger.setLevel('error'); - const groupSpy = jest.spyOn(console, 'group').mockImplementation(); - const timeSpy = jest.spyOn(console, 'time').mockImplementation(); - - logger.group('Group'); - logger.time('Timer'); - - expect(groupSpy).not.toHaveBeenCalled(); - expect(timeSpy).not.toHaveBeenCalled(); - - groupSpy.mockRestore(); - timeSpy.mockRestore(); - }); - }); - - describe('setLevel and getLevel', () => { - it('should update log level', () => { - logger = new Logger({ level: 'info' }); - expect(logger.getLevel()).toBe('info'); - - logger.setLevel('debug'); - expect(logger.getLevel()).toBe('debug'); - - logger.setLevel('error'); - expect(logger.getLevel()).toBe('error'); - }); - }); -}); \ No newline at end of file diff --git a/packages/core/src/lib/logger.ts b/packages/core/src/lib/logger.ts deleted file mode 100644 index 74d14e2..0000000 --- a/packages/core/src/lib/logger.ts +++ /dev/null @@ -1,134 +0,0 @@ -export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; - -export interface LoggerOptions { - level?: LogLevel; - prefix?: string; - timestamp?: boolean; -} - -export class Logger { - private static instance: Logger | null = null; - private level: LogLevel; - private prefix: string; - private timestamp: boolean; - - private readonly levels: Record = { - debug: 0, - info: 1, - warn: 2, - error: 3, - }; - - constructor(options: LoggerOptions = {}) { - this.level = options.level ?? (process.env['NX_PLUGIN_LOG_LEVEL'] as LogLevel) ?? 'info'; - this.prefix = options.prefix ?? '[nx-plugin-openapi]'; - this.timestamp = options.timestamp ?? true; - } - - static getInstance(options?: LoggerOptions): Logger { - if (!Logger.instance) { - Logger.instance = new Logger(options); - } - return Logger.instance; - } - - static reset(): void { - Logger.instance = null; - } - - private shouldLog(level: LogLevel): boolean { - return this.levels[level] >= this.levels[this.level]; - } - - private formatMessage(level: LogLevel, message: string, ...args: unknown[]): string { - const parts: string[] = []; - - if (this.timestamp) { - parts.push(new Date().toISOString()); - } - - if (this.prefix) { - parts.push(this.prefix); - } - - parts.push(`[${level.toUpperCase()}]`); - parts.push(message); - - if (args.length > 0) { - const formattedArgs = args.map(arg => { - if (arg instanceof Error) { - return `${arg.name}: ${arg.message}${arg.stack ? '\n' + arg.stack : ''}`; - } - if (typeof arg === 'object' && arg !== null) { - try { - return JSON.stringify(arg, null, 2); - } catch { - return String(arg); - } - } - return String(arg); - }); - parts.push(...formattedArgs); - } - - return parts.join(' '); - } - - debug(message: string, ...args: unknown[]): void { - if (this.shouldLog('debug')) { - console.debug(this.formatMessage('debug', message, ...args)); - } - } - - info(message: string, ...args: unknown[]): void { - if (this.shouldLog('info')) { - console.info(this.formatMessage('info', message, ...args)); - } - } - - warn(message: string, ...args: unknown[]): void { - if (this.shouldLog('warn')) { - console.warn(this.formatMessage('warn', message, ...args)); - } - } - - error(message: string, ...args: unknown[]): void { - if (this.shouldLog('error')) { - console.error(this.formatMessage('error', message, ...args)); - } - } - - setLevel(level: LogLevel): void { - this.level = level; - } - - getLevel(): LogLevel { - return this.level; - } - - group(label: string): void { - if (this.shouldLog('debug')) { - console.group(this.formatMessage('debug', label)); - } - } - - groupEnd(): void { - if (this.shouldLog('debug')) { - console.groupEnd(); - } - } - - time(label: string): void { - if (this.shouldLog('debug')) { - console.time(`${this.prefix} ${label}`); - } - } - - timeEnd(label: string): void { - if (this.shouldLog('debug')) { - console.timeEnd(`${this.prefix} ${label}`); - } - } -} - -export const logger = Logger.getInstance(); \ No newline at end of file diff --git a/packages/core/src/lib/plugin-loader.ts b/packages/core/src/lib/plugin-loader.ts index 46ceeb3..1e1164f 100644 --- a/packages/core/src/lib/plugin-loader.ts +++ b/packages/core/src/lib/plugin-loader.ts @@ -2,7 +2,7 @@ import { GeneratorPlugin } from './interfaces'; import { PluginLoadError, PluginNotFoundError } from './errors'; import { GeneratorRegistry } from './registry'; import { isGeneratorPlugin } from './type-guards'; -import { logger } from './logger'; +import { logger } from '@nx/devkit'; const BUILTIN_PLUGIN_MAP: Record = { 'openapi-tools': '@nx-plugin-openapi/plugin-openapi', @@ -110,11 +110,11 @@ export async function loadPlugin( const code = (e as Record)?.['code']; if (code === 'ERR_MODULE_NOT_FOUND' || /Cannot find module/.test(msg)) { - logger.error(`Plugin not found: ${name}`, { searchPaths }); + logger.error(`Plugin not found: ${name}. Searched paths: ${JSON.stringify(searchPaths)}`); throw new PluginNotFoundError(name, searchPaths); } - logger.error(`Failed to load plugin: ${name}`, e); + logger.error(`Failed to load plugin: ${name}. Error: ${e}`); throw new PluginLoadError(name, e); } } diff --git a/packages/core/src/lib/validation.ts b/packages/core/src/lib/validation.ts index d4499aa..b33ef39 100644 --- a/packages/core/src/lib/validation.ts +++ b/packages/core/src/lib/validation.ts @@ -1,7 +1,7 @@ // Core validation utilities import { ValidationError, InvalidPathError } from './errors'; import { isValidInputSpec, assertValidPath } from './type-guards'; -import { logger } from './logger'; +import { logger } from '@nx/devkit'; export interface ValidationResult { valid: boolean; diff --git a/packages/plugin-openapi/package.json b/packages/plugin-openapi/package.json index 8061d2b..4024907 100644 --- a/packages/plugin-openapi/package.json +++ b/packages/plugin-openapi/package.json @@ -3,7 +3,8 @@ "version": "0.0.1", "dependencies": { "@nx-plugin-openapi/core": "0.0.1", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "@nx/devkit": "19.8.14" }, "peerDependencies": { "@nx-plugin-openapi/core": ">=0.0.1" diff --git a/packages/plugin-openapi/src/lib/openapi-tools-generator.ts b/packages/plugin-openapi/src/lib/openapi-tools-generator.ts index 04cc5bc..4059e83 100644 --- a/packages/plugin-openapi/src/lib/openapi-tools-generator.ts +++ b/packages/plugin-openapi/src/lib/openapi-tools-generator.ts @@ -5,9 +5,9 @@ import { GeneratorContext, GeneratorPlugin, GenerateOptionsBase, - logger, ExecutionError, } from '@nx-plugin-openapi/core'; +import { logger } from '@nx/devkit'; import { buildCommandArgs, OpenApiGeneratorOptions,