@eldrforge/git-tools 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,211 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { ConsoleLogger, setLogger, getLogger, Logger } from '../src/logger';
3
+
4
+ describe('Logger utilities', () => {
5
+ describe('ConsoleLogger', () => {
6
+ let consoleSpy: {
7
+ error: ReturnType<typeof vi.spyOn>;
8
+ warn: ReturnType<typeof vi.spyOn>;
9
+ info: ReturnType<typeof vi.spyOn>;
10
+ log: ReturnType<typeof vi.spyOn>;
11
+ };
12
+
13
+ beforeEach(() => {
14
+ // Spy on console methods
15
+ consoleSpy = {
16
+ error: vi.spyOn(console, 'error').mockImplementation(() => {}),
17
+ warn: vi.spyOn(console, 'warn').mockImplementation(() => {}),
18
+ info: vi.spyOn(console, 'info').mockImplementation(() => {}),
19
+ log: vi.spyOn(console, 'log').mockImplementation(() => {}),
20
+ };
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+
27
+ it('should log error messages at error level', () => {
28
+ const logger = new ConsoleLogger('error');
29
+ logger.error('Test error message');
30
+
31
+ expect(consoleSpy.error).toHaveBeenCalledWith('Test error message');
32
+ });
33
+
34
+ it('should log warn messages at warn level', () => {
35
+ const logger = new ConsoleLogger('warn');
36
+ logger.warn('Test warning');
37
+
38
+ expect(consoleSpy.warn).toHaveBeenCalledWith('Test warning');
39
+ });
40
+
41
+ it('should log info messages at info level', () => {
42
+ const logger = new ConsoleLogger('info');
43
+ logger.info('Test info');
44
+
45
+ expect(consoleSpy.info).toHaveBeenCalledWith('Test info');
46
+ });
47
+
48
+ it('should log verbose messages at verbose level', () => {
49
+ const logger = new ConsoleLogger('verbose');
50
+ logger.verbose('Test verbose');
51
+
52
+ expect(consoleSpy.log).toHaveBeenCalledWith('[VERBOSE]', 'Test verbose');
53
+ });
54
+
55
+ it('should log debug messages at debug level', () => {
56
+ const logger = new ConsoleLogger('debug');
57
+ logger.debug('Test debug');
58
+
59
+ expect(consoleSpy.log).toHaveBeenCalledWith('[DEBUG]', 'Test debug');
60
+ });
61
+
62
+ it('should not log messages below current log level', () => {
63
+ const logger = new ConsoleLogger('error');
64
+
65
+ logger.warn('Should not log');
66
+ logger.info('Should not log');
67
+ logger.verbose('Should not log');
68
+ logger.debug('Should not log');
69
+
70
+ expect(consoleSpy.warn).not.toHaveBeenCalled();
71
+ expect(consoleSpy.info).not.toHaveBeenCalled();
72
+ expect(consoleSpy.log).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it('should log all levels when set to debug', () => {
76
+ const logger = new ConsoleLogger('debug');
77
+
78
+ logger.error('error msg');
79
+ logger.warn('warn msg');
80
+ logger.info('info msg');
81
+ logger.verbose('verbose msg');
82
+ logger.debug('debug msg');
83
+
84
+ expect(consoleSpy.error).toHaveBeenCalledWith('error msg');
85
+ expect(consoleSpy.warn).toHaveBeenCalledWith('warn msg');
86
+ expect(consoleSpy.info).toHaveBeenCalledWith('info msg');
87
+ expect(consoleSpy.log).toHaveBeenCalledWith('[VERBOSE]', 'verbose msg');
88
+ expect(consoleSpy.log).toHaveBeenCalledWith('[DEBUG]', 'debug msg');
89
+ });
90
+
91
+ it('should pass metadata to console methods', () => {
92
+ const logger = new ConsoleLogger('info');
93
+ const metadata = { userId: 123, action: 'login' };
94
+
95
+ logger.info('User action', metadata);
96
+
97
+ expect(consoleSpy.info).toHaveBeenCalledWith('User action', metadata);
98
+ });
99
+
100
+ it('should handle multiple metadata arguments', () => {
101
+ const logger = new ConsoleLogger('error');
102
+
103
+ logger.error('Error occurred', 'context1', { detail: 'value' }, 42);
104
+
105
+ expect(consoleSpy.error).toHaveBeenCalledWith('Error occurred', 'context1', { detail: 'value' }, 42);
106
+ });
107
+
108
+ it('should default to info level when not specified', () => {
109
+ const logger = new ConsoleLogger();
110
+
111
+ logger.info('Should log');
112
+ logger.verbose('Should not log');
113
+
114
+ expect(consoleSpy.info).toHaveBeenCalledWith('Should log');
115
+ expect(consoleSpy.log).not.toHaveBeenCalled();
116
+ });
117
+
118
+ it('should respect warn level threshold', () => {
119
+ const logger = new ConsoleLogger('warn');
120
+
121
+ logger.error('error - should log');
122
+ logger.warn('warn - should log');
123
+ logger.info('info - should not log');
124
+ logger.verbose('verbose - should not log');
125
+ logger.debug('debug - should not log');
126
+
127
+ expect(consoleSpy.error).toHaveBeenCalledWith('error - should log');
128
+ expect(consoleSpy.warn).toHaveBeenCalledWith('warn - should log');
129
+ expect(consoleSpy.info).not.toHaveBeenCalled();
130
+ expect(consoleSpy.log).not.toHaveBeenCalled();
131
+ });
132
+ });
133
+
134
+ describe('setLogger and getLogger', () => {
135
+ let originalLogger: Logger;
136
+
137
+ beforeEach(() => {
138
+ originalLogger = getLogger();
139
+ });
140
+
141
+ afterEach(() => {
142
+ setLogger(originalLogger);
143
+ });
144
+
145
+ it('should set and get custom logger', () => {
146
+ const customLogger: Logger = {
147
+ error: vi.fn(),
148
+ warn: vi.fn(),
149
+ info: vi.fn(),
150
+ verbose: vi.fn(),
151
+ debug: vi.fn(),
152
+ };
153
+
154
+ setLogger(customLogger);
155
+ const retrievedLogger = getLogger();
156
+
157
+ expect(retrievedLogger).toBe(customLogger);
158
+ });
159
+
160
+ it('should use custom logger for logging', () => {
161
+ const customLogger: Logger = {
162
+ error: vi.fn(),
163
+ warn: vi.fn(),
164
+ info: vi.fn(),
165
+ verbose: vi.fn(),
166
+ debug: vi.fn(),
167
+ };
168
+
169
+ setLogger(customLogger);
170
+ const logger = getLogger();
171
+
172
+ logger.error('Test error');
173
+ logger.info('Test info');
174
+
175
+ expect(customLogger.error).toHaveBeenCalledWith('Test error');
176
+ expect(customLogger.info).toHaveBeenCalledWith('Test info');
177
+ });
178
+
179
+ it('should default to ConsoleLogger', () => {
180
+ const logger = getLogger();
181
+ expect(logger).toBeDefined();
182
+ expect(typeof logger.error).toBe('function');
183
+ expect(typeof logger.info).toBe('function');
184
+ });
185
+
186
+ it('should allow replacing logger multiple times', () => {
187
+ const logger1: Logger = {
188
+ error: vi.fn(),
189
+ warn: vi.fn(),
190
+ info: vi.fn(),
191
+ verbose: vi.fn(),
192
+ debug: vi.fn(),
193
+ };
194
+
195
+ const logger2: Logger = {
196
+ error: vi.fn(),
197
+ warn: vi.fn(),
198
+ info: vi.fn(),
199
+ verbose: vi.fn(),
200
+ debug: vi.fn(),
201
+ };
202
+
203
+ setLogger(logger1);
204
+ expect(getLogger()).toBe(logger1);
205
+
206
+ setLogger(logger2);
207
+ expect(getLogger()).toBe(logger2);
208
+ });
209
+ });
210
+ });
211
+
package/tests/setup.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { vi } from 'vitest';
2
+
3
+ // Mock console methods to prevent output during tests
4
+ // global.console = {
5
+ // ...console,
6
+ // // Keep error logging for debugging
7
+ // error: vi.fn(),
8
+ // // Suppress other console output
9
+ // log: vi.fn(),
10
+ // warn: vi.fn(),
11
+ // info: vi.fn(),
12
+ // debug: vi.fn(),
13
+ // };
14
+
15
+ // Mock process.exit to prevent actual process termination
16
+ process.exit = vi.fn() as unknown as (code?: number) => never;
17
+
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ safeJsonParse,
4
+ validateString,
5
+ validateHasProperty,
6
+ validatePackageJson
7
+ } from '../src/validation';
8
+
9
+ describe('Validation utilities', () => {
10
+ describe('safeJsonParse', () => {
11
+ it('should parse valid JSON string', () => {
12
+ const jsonString = '{"name": "test", "value": 123}';
13
+ const result = safeJsonParse(jsonString, 'test context');
14
+ expect(result).toEqual({ name: 'test', value: 123 });
15
+ });
16
+
17
+ it('should throw error for invalid JSON with context', () => {
18
+ const invalidJson = '{"invalid": json}';
19
+ expect(() => safeJsonParse(invalidJson, 'test file')).toThrow('Failed to parse JSON (test file):');
20
+ });
21
+
22
+ it('should throw error for empty string', () => {
23
+ expect(() => safeJsonParse('', 'empty context')).toThrow('Failed to parse JSON (empty context):');
24
+ });
25
+
26
+ it('should parse JSON arrays', () => {
27
+ const jsonArray = '[1, 2, 3]';
28
+ const result = safeJsonParse(jsonArray, 'array context');
29
+ expect(result).toEqual([1, 2, 3]);
30
+ });
31
+
32
+ it('should parse JSON primitives', () => {
33
+ expect(safeJsonParse('123', 'number context')).toBe(123);
34
+ expect(safeJsonParse('"hello"', 'string context')).toBe('hello');
35
+ expect(safeJsonParse('true', 'boolean context')).toBe(true);
36
+ expect(() => safeJsonParse('null', 'null context')).toThrow('Failed to parse JSON (null context): Parsed JSON is null or undefined');
37
+ });
38
+ });
39
+
40
+ describe('validateString', () => {
41
+ it('should validate valid string', () => {
42
+ const result = validateString('test string', 'test value');
43
+ expect(result).toBe('test string');
44
+ });
45
+
46
+ it('should throw error for non-string value', () => {
47
+ expect(() => validateString(123, 'number value')).toThrow('number value must be a string');
48
+ expect(() => validateString(null, 'null value')).toThrow('null value must be a string');
49
+ expect(() => validateString(undefined, 'undefined value')).toThrow('undefined value must be a string');
50
+ });
51
+
52
+ it('should throw error for empty string', () => {
53
+ expect(() => validateString('', 'empty string')).toThrow('empty string cannot be empty');
54
+ });
55
+ });
56
+
57
+ describe('validateHasProperty', () => {
58
+ it('should validate object has required property', () => {
59
+ const obj = { name: 'test', value: 123 };
60
+ validateHasProperty(obj, 'name', 'test object');
61
+ validateHasProperty(obj, 'value', 'test object');
62
+ });
63
+
64
+ it('should throw error for missing property', () => {
65
+ const obj = { name: 'test' };
66
+ expect(() => validateHasProperty(obj, 'value', 'test object')).toThrow("Missing required property 'value' in test object");
67
+ });
68
+
69
+ it('should throw error for null/undefined object', () => {
70
+ expect(() => validateHasProperty(null, 'name', 'null object')).toThrow('Object is null or not an object in null object');
71
+ expect(() => validateHasProperty(undefined, 'name', 'undefined object')).toThrow('Object is null or not an object in undefined object');
72
+ });
73
+
74
+ it('should work with nested properties', () => {
75
+ const obj = { nested: { value: 'test' } };
76
+ validateHasProperty(obj, 'nested', 'test object');
77
+ validateHasProperty(obj.nested, 'value', 'nested object');
78
+ });
79
+ });
80
+
81
+ describe('validatePackageJson', () => {
82
+ it('should validate valid package.json', () => {
83
+ const validPackage = {
84
+ name: '@test/package',
85
+ version: '1.0.0',
86
+ description: 'Test package',
87
+ dependencies: {
88
+ 'react': '^18.0.0'
89
+ }
90
+ };
91
+
92
+ const result = validatePackageJson(validPackage, '/path/to/package.json');
93
+ expect(result).toEqual(validPackage);
94
+ });
95
+
96
+ it('should validate minimal package.json', () => {
97
+ const minimalPackage = {
98
+ name: 'minimal-package',
99
+ version: '0.1.0'
100
+ };
101
+
102
+ const result = validatePackageJson(minimalPackage, '/path/to/package.json');
103
+ expect(result).toEqual(minimalPackage);
104
+ });
105
+
106
+ it('should throw error for missing name', () => {
107
+ const invalidPackage = {
108
+ version: '1.0.0'
109
+ };
110
+ expect(() => validatePackageJson(invalidPackage, '/path/to/package.json')).toThrow('Invalid package.json (/path/to/package.json): name must be a string');
111
+ });
112
+
113
+ it('should throw error for invalid name type', () => {
114
+ const invalidPackage = {
115
+ name: 123,
116
+ version: '1.0.0'
117
+ };
118
+ expect(() => validatePackageJson(invalidPackage, '/path/to/package.json')).toThrow('Invalid package.json (/path/to/package.json): name must be a string');
119
+ });
120
+
121
+ it('should throw error for null/undefined input', () => {
122
+ expect(() => validatePackageJson(null, '/path/to/package.json')).toThrow('Invalid package.json (/path/to/package.json): not an object');
123
+ expect(() => validatePackageJson(undefined, '/path/to/package.json')).toThrow('Invalid package.json (/path/to/package.json): not an object');
124
+ });
125
+
126
+ it('should allow missing version', () => {
127
+ const packageWithoutVersion = {
128
+ name: 'test-package'
129
+ };
130
+
131
+ const result = validatePackageJson(packageWithoutVersion, '/path/to/package.json');
132
+ expect(result).toEqual(packageWithoutVersion);
133
+ });
134
+
135
+ it('should validate dependencies object', () => {
136
+ const packageWithDeps = {
137
+ name: 'test-package',
138
+ version: '1.0.0',
139
+ dependencies: {
140
+ 'lodash': '^4.17.21',
141
+ 'react': '^18.0.0'
142
+ },
143
+ devDependencies: {
144
+ 'jest': '^29.0.0'
145
+ }
146
+ };
147
+
148
+ const result = validatePackageJson(packageWithDeps, '/path/to/package.json');
149
+ expect(result).toEqual(packageWithDeps);
150
+ });
151
+ });
152
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true,
9
+ "outDir": "./dist",
10
+ "rootDir": ".",
11
+ "types": [
12
+ "node",
13
+ "vitest"
14
+ ],
15
+ "incremental": true,
16
+ "allowJs": true,
17
+ "resolveJsonModule": true,
18
+ "declaration": true,
19
+ "declarationMap": true,
20
+ "baseUrl": ".",
21
+ "paths": {
22
+ "*": [
23
+ "src/*"
24
+ ]
25
+ }
26
+ },
27
+ "include": [
28
+ "src/**/*",
29
+ "tests/**/*"
30
+ ],
31
+ "exclude": [
32
+ "node_modules"
33
+ ]
34
+ }
35
+
package/vite.config.ts ADDED
@@ -0,0 +1,78 @@
1
+ import { defineConfig } from 'vite';
2
+ import { VitePluginNode } from 'vite-plugin-node';
3
+ import { execSync } from 'child_process';
4
+ import dts from 'vite-plugin-dts';
5
+
6
+ let gitInfo = {
7
+ branch: '',
8
+ commit: '',
9
+ tags: '',
10
+ commitDate: '',
11
+ };
12
+
13
+ try {
14
+ gitInfo = {
15
+ branch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
16
+ commit: execSync('git rev-parse --short HEAD').toString().trim(),
17
+ tags: '',
18
+ commitDate: execSync('git log -1 --format=%cd --date=iso').toString().trim(),
19
+ };
20
+
21
+ try {
22
+ gitInfo.tags = execSync('git tag --points-at HEAD | paste -sd "," -').toString().trim();
23
+ } catch {
24
+ gitInfo.tags = '';
25
+ }
26
+ } catch {
27
+ // eslint-disable-next-line no-console
28
+ console.log('Directory does not have a Git repository, skipping git info');
29
+ }
30
+
31
+ export default defineConfig({
32
+ server: {
33
+ port: 3000
34
+ },
35
+ plugins: [
36
+ ...VitePluginNode({
37
+ adapter: 'express',
38
+ appPath: './src/index.ts',
39
+ exportName: 'viteNodeApp',
40
+ tsCompiler: 'swc',
41
+ swcOptions: {
42
+ sourceMaps: true,
43
+ },
44
+ }),
45
+ dts({
46
+ include: ['src/**/*'],
47
+ exclude: ['tests/**/*', 'node_modules/**/*'],
48
+ rollupTypes: true,
49
+ }),
50
+ ],
51
+ build: {
52
+ target: 'esnext',
53
+ outDir: 'dist',
54
+ lib: {
55
+ entry: './src/index.ts',
56
+ formats: ['es'],
57
+ },
58
+ rollupOptions: {
59
+ external: [
60
+ 'semver',
61
+ 'shell-escape',
62
+ 'winston'
63
+ ],
64
+ input: 'src/index.ts',
65
+ output: {
66
+ format: 'esm',
67
+ entryFileNames: '[name].js',
68
+ preserveModules: true,
69
+ exports: 'named',
70
+ },
71
+ },
72
+ // Make sure Vite generates ESM-compatible code
73
+ modulePreload: false,
74
+ minify: false,
75
+ sourcemap: true
76
+ },
77
+ });
78
+
@@ -0,0 +1,37 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: false,
6
+ environment: 'node',
7
+ include: ['tests/**/*.test.ts'],
8
+ env: {
9
+ TZ: 'America/New_York'
10
+ },
11
+ // Add pool configuration to prevent memory issues
12
+ pool: 'forks',
13
+ poolOptions: {
14
+ forks: {
15
+ maxForks: 2,
16
+ minForks: 1
17
+ }
18
+ },
19
+ // Add test timeout and memory limits
20
+ testTimeout: 30000,
21
+ hookTimeout: 10000,
22
+ teardownTimeout: 10000,
23
+ coverage: {
24
+ provider: 'v8',
25
+ reporter: ['text', 'lcov', 'html'],
26
+ all: true,
27
+ include: ['src/**/*.ts'],
28
+ thresholds: {
29
+ statements: 80,
30
+ branches: 80,
31
+ functions: 80,
32
+ lines: 80,
33
+ }
34
+ },
35
+ },
36
+ });
37
+