@eldrforge/git-tools 0.1.3 → 0.1.4

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.
package/src/index.ts DELETED
@@ -1,58 +0,0 @@
1
- /**
2
- * @eldrforge/git-tools
3
- *
4
- * Git utilities for automation - secure process execution and Git operations
5
- *
6
- * @module git-tools
7
- */
8
-
9
- // Export logger interface and functions
10
- export type { Logger } from './logger';
11
- export {
12
- ConsoleLogger,
13
- setLogger,
14
- getLogger
15
- } from './logger';
16
-
17
- // Export child process execution functions
18
- export {
19
- runSecure,
20
- runSecureWithInheritedStdio,
21
- run,
22
- runWithInheritedStdio,
23
- runWithDryRunSupport,
24
- runSecureWithDryRunSupport,
25
- validateGitRef,
26
- validateFilePath,
27
- escapeShellArg
28
- } from './child';
29
-
30
- // Export validation utilities
31
- export {
32
- safeJsonParse,
33
- validateString,
34
- validateHasProperty,
35
- validatePackageJson
36
- } from './validation';
37
-
38
- // Export git utilities
39
- export {
40
- isValidGitRef,
41
- findPreviousReleaseTag,
42
- getCurrentVersion,
43
- getDefaultFromRef,
44
- getRemoteDefaultBranch,
45
- localBranchExists,
46
- remoteBranchExists,
47
- getBranchCommitSha,
48
- isBranchInSyncWithRemote,
49
- safeSyncBranchWithRemote,
50
- getCurrentBranch,
51
- getGitStatusSummary,
52
- getGloballyLinkedPackages,
53
- getLinkedDependencies,
54
- getLinkCompatibilityProblems,
55
- getLinkProblems,
56
- isNpmLinked
57
- } from './git';
58
-
package/src/logger.ts DELETED
@@ -1,81 +0,0 @@
1
- /**
2
- * Minimal logging interface that git-tools requires.
3
- * Allows consumers to provide their own logger implementation (e.g., Winston, console, etc.)
4
- */
5
- export interface Logger {
6
- error(message: string, ...meta: any[]): void;
7
- warn(message: string, ...meta: any[]): void;
8
- info(message: string, ...meta: any[]): void;
9
- verbose(message: string, ...meta: any[]): void;
10
- debug(message: string, ...meta: any[]): void;
11
- }
12
-
13
- /**
14
- * Default console-based logger implementation
15
- */
16
- export class ConsoleLogger implements Logger {
17
- constructor(private level: string = 'info') {}
18
-
19
- private shouldLog(level: string): boolean {
20
- const levels = ['error', 'warn', 'info', 'verbose', 'debug'];
21
- const currentLevelIndex = levels.indexOf(this.level);
22
- const messageLevelIndex = levels.indexOf(level);
23
- return messageLevelIndex <= currentLevelIndex;
24
- }
25
-
26
- error(message: string, ...meta: any[]): void {
27
- if (this.shouldLog('error')) {
28
- // eslint-disable-next-line no-console
29
- console.error(message, ...meta);
30
- }
31
- }
32
-
33
- warn(message: string, ...meta: any[]): void {
34
- if (this.shouldLog('warn')) {
35
- // eslint-disable-next-line no-console
36
- console.warn(message, ...meta);
37
- }
38
- }
39
-
40
- info(message: string, ...meta: any[]): void {
41
- if (this.shouldLog('info')) {
42
- // eslint-disable-next-line no-console
43
- console.info(message, ...meta);
44
- }
45
- }
46
-
47
- verbose(message: string, ...meta: any[]): void {
48
- if (this.shouldLog('verbose')) {
49
- // eslint-disable-next-line no-console
50
- console.log('[VERBOSE]', message, ...meta);
51
- }
52
- }
53
-
54
- debug(message: string, ...meta: any[]): void {
55
- if (this.shouldLog('debug')) {
56
- // eslint-disable-next-line no-console
57
- console.log('[DEBUG]', message, ...meta);
58
- }
59
- }
60
- }
61
-
62
- /**
63
- * Global logger instance - defaults to console logger
64
- * Use setLogger() to replace with your own implementation
65
- */
66
- let globalLogger: Logger = new ConsoleLogger();
67
-
68
- /**
69
- * Set the global logger instance
70
- */
71
- export function setLogger(logger: Logger): void {
72
- globalLogger = logger;
73
- }
74
-
75
- /**
76
- * Get the global logger instance
77
- */
78
- export function getLogger(): Logger {
79
- return globalLogger;
80
- }
81
-
package/src/validation.ts DELETED
@@ -1,62 +0,0 @@
1
- /**
2
- * Runtime validation utilities for safe type handling
3
- */
4
-
5
- /**
6
- * Safely parses JSON with error handling
7
- */
8
- export const safeJsonParse = <T = any>(jsonString: string, context?: string): T => {
9
- try {
10
- const parsed = JSON.parse(jsonString);
11
- if (parsed === null || parsed === undefined) {
12
- throw new Error('Parsed JSON is null or undefined');
13
- }
14
- return parsed;
15
- } catch (error) {
16
- const contextStr = context ? ` (${context})` : '';
17
- throw new Error(`Failed to parse JSON${contextStr}: ${error instanceof Error ? error.message : 'Unknown error'}`);
18
- }
19
- };
20
-
21
- /**
22
- * Validates that a value is a non-empty string
23
- */
24
- export const validateString = (value: any, fieldName: string): string => {
25
- if (typeof value !== 'string') {
26
- throw new Error(`${fieldName} must be a string, got ${typeof value}`);
27
- }
28
- if (value.trim() === '') {
29
- throw new Error(`${fieldName} cannot be empty`);
30
- }
31
- return value;
32
- };
33
-
34
- /**
35
- * Validates that a value exists and has a specific property
36
- */
37
- export const validateHasProperty = (obj: any, property: string, context?: string): void => {
38
- if (!obj || typeof obj !== 'object') {
39
- const contextStr = context ? ` in ${context}` : '';
40
- throw new Error(`Object is null or not an object${contextStr}`);
41
- }
42
- if (!(property in obj)) {
43
- const contextStr = context ? ` in ${context}` : '';
44
- throw new Error(`Missing required property '${property}'${contextStr}`);
45
- }
46
- };
47
-
48
- /**
49
- * Validates package.json structure has basic required fields
50
- */
51
- export const validatePackageJson = (data: any, context?: string, requireName: boolean = true): any => {
52
- if (!data || typeof data !== 'object') {
53
- const contextStr = context ? ` (${context})` : '';
54
- throw new Error(`Invalid package.json${contextStr}: not an object`);
55
- }
56
- if (requireName && typeof data.name !== 'string') {
57
- const contextStr = context ? ` (${context})` : '';
58
- throw new Error(`Invalid package.json${contextStr}: name must be a string`);
59
- }
60
- return data;
61
- };
62
-
@@ -1,170 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { runSecure, runSecureWithInheritedStdio, validateGitRef, validateFilePath, escapeShellArg } from '../src/child';
3
-
4
- /**
5
- * Integration tests that actually execute commands (not mocked)
6
- * These test the real implementation paths
7
- */
8
- describe('child.ts - integration tests', () => {
9
- describe('runSecure', () => {
10
- it('should execute simple echo command', async () => {
11
- const { stdout } = await runSecure('echo', ['hello']);
12
- expect(stdout.trim()).toBe('hello');
13
- });
14
-
15
- it('should handle multiple arguments', async () => {
16
- const { stdout } = await runSecure('echo', ['hello', 'world']);
17
- expect(stdout.trim()).toContain('hello');
18
- expect(stdout.trim()).toContain('world');
19
- });
20
-
21
- it('should capture stderr', async () => {
22
- // Use a command that writes to stderr
23
- const { stderr } = await runSecure('node', ['-e', 'console.error("test error")']);
24
- expect(stderr.trim()).toBe('test error');
25
- });
26
-
27
- it('should throw error for non-existent command', async () => {
28
- await expect(runSecure('nonexistent-command-12345', [])).rejects.toThrow();
29
- });
30
-
31
- it('should throw error for command with non-zero exit code', async () => {
32
- await expect(runSecure('node', ['-e', 'process.exit(1)'])).rejects.toThrow();
33
- });
34
-
35
- it('should handle empty args array', async () => {
36
- const { stdout } = await runSecure('pwd', []);
37
- expect(stdout).toBeTruthy();
38
- });
39
- });
40
-
41
- describe('runSecureWithInheritedStdio', () => {
42
- it('should execute command successfully', async () => {
43
- await expect(runSecureWithInheritedStdio('echo', ['test'])).resolves.toBeUndefined();
44
- });
45
-
46
- it('should throw error for command with non-zero exit code', async () => {
47
- await expect(runSecureWithInheritedStdio('node', ['-e', 'process.exit(1)'])).rejects.toThrow();
48
- });
49
-
50
- it('should throw error for non-existent command', async () => {
51
- await expect(runSecureWithInheritedStdio('nonexistent-cmd-98765', [])).rejects.toThrow();
52
- });
53
- });
54
-
55
- describe('validateGitRef', () => {
56
- it('should return true for valid simple ref', () => {
57
- expect(validateGitRef('main')).toBe(true);
58
- expect(validateGitRef('feature-branch')).toBe(true);
59
- expect(validateGitRef('v1.2.3')).toBe(true);
60
- });
61
-
62
- it('should return true for refs with slashes', () => {
63
- expect(validateGitRef('refs/heads/main')).toBe(true);
64
- expect(validateGitRef('origin/main')).toBe(true);
65
- expect(validateGitRef('release/v1.2.x')).toBe(true);
66
- });
67
-
68
- it('should return true for refs with dots', () => {
69
- expect(validateGitRef('v1.2.3')).toBe(true);
70
- expect(validateGitRef('release/1.2.x')).toBe(true);
71
- });
72
-
73
- it('should return false for refs with double dots (directory traversal)', () => {
74
- expect(validateGitRef('../etc/passwd')).toBe(false);
75
- expect(validateGitRef('refs/../../../etc')).toBe(false);
76
- });
77
-
78
- it('should return false for refs starting with dash (flag injection)', () => {
79
- expect(validateGitRef('-rf')).toBe(false);
80
- expect(validateGitRef('--delete')).toBe(false);
81
- });
82
-
83
- it('should return false for refs with shell metacharacters', () => {
84
- expect(validateGitRef('main; rm -rf /')).toBe(false);
85
- expect(validateGitRef('main|cat /etc/passwd')).toBe(false);
86
- expect(validateGitRef('main&whoami')).toBe(false);
87
- expect(validateGitRef('main`whoami`')).toBe(false);
88
- expect(validateGitRef('main$(whoami)')).toBe(false);
89
- });
90
-
91
- it('should return false for refs with spaces', () => {
92
- expect(validateGitRef('main branch')).toBe(false);
93
- expect(validateGitRef('feature test')).toBe(false);
94
- });
95
-
96
- it('should return false for refs with brackets', () => {
97
- expect(validateGitRef('main[test]')).toBe(false);
98
- expect(validateGitRef('branch{test}')).toBe(false);
99
- });
100
-
101
- it('should return true for valid refs with underscores', () => {
102
- expect(validateGitRef('feature_branch')).toBe(true);
103
- expect(validateGitRef('test_123')).toBe(true);
104
- });
105
-
106
- it('should return true for valid refs with numbers', () => {
107
- expect(validateGitRef('v1234567890')).toBe(true);
108
- expect(validateGitRef('release123')).toBe(true);
109
- });
110
- });
111
-
112
- describe('validateFilePath', () => {
113
- it('should return true for valid file paths', () => {
114
- expect(validateFilePath('/path/to/file.txt')).toBe(true);
115
- expect(validateFilePath('./relative/path.js')).toBe(true);
116
- expect(validateFilePath('../parent/file.md')).toBe(true);
117
- });
118
-
119
- it('should return false for paths with shell metacharacters', () => {
120
- expect(validateFilePath('/path;rm -rf /')).toBe(false);
121
- expect(validateFilePath('/path|cat /etc/passwd')).toBe(false);
122
- expect(validateFilePath('/path&whoami')).toBe(false);
123
- expect(validateFilePath('/path`whoami`')).toBe(false);
124
- expect(validateFilePath('/path$(whoami)')).toBe(false);
125
- });
126
-
127
- it('should return false for paths with brackets', () => {
128
- expect(validateFilePath('/path[test]')).toBe(false);
129
- expect(validateFilePath('/path{test}')).toBe(false);
130
- });
131
-
132
- it('should return true for paths with spaces and dots', () => {
133
- expect(validateFilePath('/path with spaces/file.txt')).toBe(true);
134
- expect(validateFilePath('/path/to/../file.txt')).toBe(true);
135
- expect(validateFilePath('./file.txt')).toBe(true);
136
- });
137
- });
138
-
139
- describe('escapeShellArg', () => {
140
- it('should escape single quotes on Unix', () => {
141
- if (process.platform !== 'win32') {
142
- const result = escapeShellArg("test'string");
143
- expect(result).toContain("'\\''");
144
- }
145
- });
146
-
147
- it('should escape double quotes on Windows', () => {
148
- if (process.platform === 'win32') {
149
- const result = escapeShellArg('test"string');
150
- expect(result).toContain('\\"');
151
- }
152
- });
153
-
154
- it('should handle normal strings without special chars', () => {
155
- const result = escapeShellArg('normal-string-123');
156
- expect(result).toBeTruthy();
157
- });
158
-
159
- it('should handle empty string', () => {
160
- const result = escapeShellArg('');
161
- expect(result).toBeTruthy();
162
- });
163
-
164
- it('should handle strings with spaces', () => {
165
- const result = escapeShellArg('string with spaces');
166
- expect(result).toBeTruthy();
167
- });
168
- });
169
- });
170
-