@eldrforge/git-tools 0.1.3 → 0.1.5

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/eslint.config.mjs DELETED
@@ -1,84 +0,0 @@
1
- import { defineConfig, globalIgnores } from "eslint/config";
2
- import typescriptEslint from "@typescript-eslint/eslint-plugin";
3
- import importPlugin from "eslint-plugin-import";
4
- import globals from "globals";
5
- import tsParser from "@typescript-eslint/parser";
6
- import path from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import js from "@eslint/js";
9
- import { FlatCompat } from "@eslint/eslintrc";
10
-
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = path.dirname(__filename);
13
- const compat = new FlatCompat({
14
- baseDirectory: __dirname,
15
- recommendedConfig: js.configs.recommended,
16
- allConfig: js.configs.all
17
- });
18
-
19
- export default defineConfig([
20
- globalIgnores([
21
- "dist/**",
22
- "node_modules/**",
23
- "**/*.test.ts",
24
- "temp-dist/**",
25
- ]),
26
- {
27
- extends: compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
28
-
29
- plugins: {
30
- "@typescript-eslint": typescriptEslint,
31
- "import": importPlugin,
32
- },
33
-
34
- languageOptions: {
35
- globals: {
36
- ...globals.node,
37
- },
38
-
39
- parser: tsParser,
40
- ecmaVersion: "latest",
41
- sourceType: "module",
42
- },
43
-
44
- rules: {
45
- "@typescript-eslint/no-explicit-any": "off",
46
- "@typescript-eslint/explicit-function-return-type": "off",
47
-
48
- "@typescript-eslint/no-unused-vars": ["warn", {
49
- argsIgnorePattern: "^_",
50
- }],
51
-
52
- indent: ["warn", 4, {
53
- SwitchCase: 1,
54
- }],
55
-
56
- "import/extensions": ["error", "never", {
57
- ignorePackages: true,
58
- pattern: {
59
- "js": "never",
60
- "ts": "never",
61
- "d": "always"
62
- }
63
- }],
64
-
65
- "import/no-extraneous-dependencies": ["error", {
66
- devDependencies: true,
67
- optionalDependencies: false,
68
- peerDependencies: false,
69
- }],
70
-
71
- "no-console": ["error"],
72
-
73
- "no-restricted-imports": ["error", {
74
- paths: [],
75
- patterns: [
76
- {
77
- group: ["src/**"],
78
- message: "Use absolute imports instead of relative imports"
79
- }
80
- ]
81
- }]
82
- },
83
- }]);
84
-
package/src/child.ts DELETED
@@ -1,249 +0,0 @@
1
- #!/usr/bin/env node
2
- import child_process, { exec, spawn } from 'child_process';
3
- import util from 'util';
4
- import { getLogger } from './logger';
5
-
6
- /**
7
- * Escapes shell arguments to prevent command injection
8
- */
9
- function escapeShellArg(arg: string): string {
10
- // For Windows, we need different escaping
11
- if (process.platform === 'win32') {
12
- // Escape double quotes and backslashes
13
- return `"${arg.replace(/[\\"]/g, '\\$&')}"`;
14
- } else {
15
- // For Unix-like systems, escape single quotes
16
- return `'${arg.replace(/'/g, "'\\''")}'`;
17
- }
18
- }
19
-
20
- /**
21
- * Validates git references to prevent injection
22
- */
23
- function validateGitRef(ref: string): boolean {
24
- // Git refs can contain letters, numbers, hyphens, underscores, slashes, and dots
25
- // But cannot contain certain dangerous characters
26
- const validRefPattern = /^[a-zA-Z0-9._/-]+$/;
27
- const invalidPatterns = [
28
- /\.\./, // No double dots (directory traversal)
29
- /^-/, // Cannot start with dash (flag injection)
30
- /[\s;<>|&`$(){}[\]]/ // No shell metacharacters
31
- ];
32
-
33
- if (!validRefPattern.test(ref)) {
34
- return false;
35
- }
36
-
37
- return !invalidPatterns.some(pattern => pattern.test(ref));
38
- }
39
-
40
- /**
41
- * Validates file paths to prevent injection
42
- */
43
- function validateFilePath(filePath: string): boolean {
44
- // Basic validation - no shell metacharacters
45
- const invalidChars = /[;<>|&`$(){}[\]]/;
46
- return !invalidChars.test(filePath);
47
- }
48
-
49
- /**
50
- * Securely executes a command with arguments array (no shell injection risk)
51
- */
52
- export async function runSecure(
53
- command: string,
54
- args: string[] = [],
55
- options: child_process.SpawnOptions = {}
56
- ): Promise<{ stdout: string; stderr: string }> {
57
- const logger = getLogger();
58
-
59
- return new Promise((resolve, reject) => {
60
- logger.verbose(`Executing command securely: ${command} ${args.join(' ')}`);
61
- logger.verbose(`Working directory: ${options?.cwd || process.cwd()}`);
62
-
63
- const child = spawn(command, args, {
64
- ...options,
65
- shell: false, // CRITICAL: Never use shell for user input
66
- stdio: 'pipe'
67
- });
68
-
69
- let stdout = '';
70
- let stderr = '';
71
-
72
- if (child.stdout) {
73
- child.stdout.on('data', (data) => {
74
- stdout += data.toString();
75
- });
76
- }
77
-
78
- if (child.stderr) {
79
- child.stderr.on('data', (data) => {
80
- stderr += data.toString();
81
- });
82
- }
83
-
84
- child.on('close', (code) => {
85
- if (code === 0) {
86
- logger.verbose(`Command completed successfully`);
87
- logger.verbose(`stdout: ${stdout}`);
88
- if (stderr) {
89
- logger.verbose(`stderr: ${stderr}`);
90
- }
91
- resolve({ stdout, stderr });
92
- } else {
93
- logger.error(`Command failed with exit code ${code}`);
94
- logger.error(`stdout: ${stdout}`);
95
- logger.error(`stderr: ${stderr}`);
96
- reject(new Error(`Command "${[command, ...args].join(' ')}" failed with exit code ${code}`));
97
- }
98
- });
99
-
100
- child.on('error', (error) => {
101
- logger.error(`Command failed to start: ${error.message}`);
102
- reject(error);
103
- });
104
- });
105
- }
106
-
107
- /**
108
- * Securely executes a command with inherited stdio (no shell injection risk)
109
- */
110
- export async function runSecureWithInheritedStdio(
111
- command: string,
112
- args: string[] = [],
113
- options: child_process.SpawnOptions = {}
114
- ): Promise<void> {
115
- const logger = getLogger();
116
-
117
- return new Promise((resolve, reject) => {
118
- logger.verbose(`Executing command securely with inherited stdio: ${command} ${args.join(' ')}`);
119
- logger.verbose(`Working directory: ${options?.cwd || process.cwd()}`);
120
-
121
- const child = spawn(command, args, {
122
- ...options,
123
- shell: false, // CRITICAL: Never use shell for user input
124
- stdio: 'inherit'
125
- });
126
-
127
- child.on('close', (code) => {
128
- if (code === 0) {
129
- logger.verbose(`Command completed successfully with code ${code}`);
130
- resolve();
131
- } else {
132
- logger.error(`Command failed with exit code ${code}`);
133
- reject(new Error(`Command "${[command, ...args].join(' ')}" failed with exit code ${code}`));
134
- }
135
- });
136
-
137
- child.on('error', (error) => {
138
- logger.error(`Command failed to start: ${error.message}`);
139
- reject(error);
140
- });
141
- });
142
- }
143
-
144
- export async function run(command: string, options: child_process.ExecOptions = {}): Promise<{ stdout: string; stderr: string }> {
145
- const logger = getLogger();
146
- const execPromise = util.promisify(exec);
147
-
148
- // Ensure encoding is set to 'utf8' to get string output instead of Buffer
149
- const execOptions = { encoding: 'utf8' as const, ...options };
150
-
151
- logger.verbose(`Executing command: ${command}`);
152
- logger.verbose(`Working directory: ${execOptions?.cwd || process.cwd()}`);
153
- logger.verbose(`Environment variables: ${Object.keys(execOptions?.env || process.env).length} variables`);
154
-
155
- try {
156
- const result = await execPromise(command, execOptions);
157
- logger.verbose(`Command completed successfully`);
158
- logger.verbose(`stdout: ${result.stdout}`);
159
- if (result.stderr) {
160
- logger.verbose(`stderr: ${result.stderr}`);
161
- }
162
- // Ensure result is properly typed as strings
163
- return {
164
- stdout: String(result.stdout),
165
- stderr: String(result.stderr)
166
- };
167
- } catch (error: any) {
168
- logger.error(`Command failed: ${command}`);
169
- logger.error(`Error: ${error.message}`);
170
- logger.error(`Exit code: ${error.code}`);
171
- logger.error(`Signal: ${error.signal}`);
172
- if (error.stdout) {
173
- logger.error(`stdout: ${error.stdout}`);
174
- }
175
- if (error.stderr) {
176
- logger.error(`stderr: ${error.stderr}`);
177
- }
178
- throw error;
179
- }
180
- }
181
-
182
- /**
183
- * @deprecated Use runSecureWithInheritedStdio instead for better security
184
- * Legacy function for backward compatibility - parses shell command string
185
- */
186
- export async function runWithInheritedStdio(command: string, options: child_process.ExecOptions = {}): Promise<void> {
187
-
188
- // Parse command to extract command and arguments safely
189
- const parts = command.trim().split(/\s+/);
190
- if (parts.length === 0) {
191
- throw new Error('Empty command provided');
192
- }
193
-
194
- const cmd = parts[0];
195
- const args = parts.slice(1);
196
-
197
- // Use the secure version
198
- return runSecureWithInheritedStdio(cmd, args, options);
199
- }
200
-
201
- export async function runWithDryRunSupport(
202
- command: string,
203
- isDryRun: boolean,
204
- options: child_process.ExecOptions = {},
205
- useInheritedStdio: boolean = false
206
- ): Promise<{ stdout: string; stderr: string }> {
207
- const logger = getLogger();
208
-
209
- if (isDryRun) {
210
- logger.info(`DRY RUN: Would execute command: ${command}`);
211
- return { stdout: '', stderr: '' };
212
- }
213
-
214
- if (useInheritedStdio) {
215
- await runWithInheritedStdio(command, options);
216
- return { stdout: '', stderr: '' }; // No output captured when using inherited stdio
217
- }
218
-
219
- return run(command, options);
220
- }
221
-
222
- /**
223
- * Secure version of runWithDryRunSupport using argument arrays
224
- */
225
- export async function runSecureWithDryRunSupport(
226
- command: string,
227
- args: string[] = [],
228
- isDryRun: boolean,
229
- options: child_process.SpawnOptions = {},
230
- useInheritedStdio: boolean = false
231
- ): Promise<{ stdout: string; stderr: string }> {
232
- const logger = getLogger();
233
-
234
- if (isDryRun) {
235
- logger.info(`DRY RUN: Would execute command: ${command} ${args.join(' ')}`);
236
- return { stdout: '', stderr: '' };
237
- }
238
-
239
- if (useInheritedStdio) {
240
- await runSecureWithInheritedStdio(command, args, options);
241
- return { stdout: '', stderr: '' }; // No output captured when using inherited stdio
242
- }
243
-
244
- return runSecure(command, args, options);
245
- }
246
-
247
- // Export validation functions for use in other modules
248
- export { validateGitRef, validateFilePath, escapeShellArg };
249
-