@iamandersonp/prettier-staged 0.2.0

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/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # @iamandersonp/prettier-staged
2
+
3
+ An utitlty to auto format stagged files using prettier
4
+
5
+ ## Installation
6
+
7
+ To use as a dev dependency
8
+
9
+ ```bash
10
+ npm i -D @iamandersonp/prettier-staged
11
+ ```
12
+
13
+ To use as a global
14
+
15
+ ```bash
16
+ npm i -g @iamandersonp/prettier-staged
17
+ ```
18
+
19
+ ## Setup
20
+
21
+ Create a command on your package.json
22
+
23
+ ```json
24
+ "prettier-staged": "prettier-staged",
25
+ ```
26
+
27
+ ## Automatic Hook Installation
28
+
29
+ When installed as a dependency (not during local development), prettier-staged automatically copies a pre-commit hook to your project's hooks directory. This provides a ready-to-use solution for formatting staged files.
30
+
31
+ ### Configuration
32
+
33
+ You can configure the hooks directory using an optional `.env` file in your project root:
34
+
35
+ ```bash
36
+ # .env (optional)
37
+ HOOKS_DIR=.git-hooks # Default value
38
+ # or customize:
39
+ HOOKS_DIR=custom-hooks
40
+ HOOKS_DIR="my hooks" # With spaces (quotes optional)
41
+ ```
42
+
43
+ If no `.env` file exists, the default directory `.git-hooks` will be used.
44
+
45
+ #### Setting up your .env file
46
+
47
+ 1. Copy the example configuration:
48
+
49
+ ```bash
50
+ cp .env.example .env
51
+ ```
52
+
53
+ 2. Edit `.env` to customize your hooks directory (optional):
54
+
55
+ ```bash
56
+ HOOKS_DIR=.git-hooks # Use default
57
+ # or
58
+ HOOKS_DIR=git-hooks # Custom directory
59
+ ```
60
+
61
+ ### What gets installed
62
+
63
+ - A `{HOOKS_DIR}/pre-commit` file that:
64
+ - Skips formatting during merge conflicts
65
+ - Formats staged files with Prettier
66
+ - Re-stages formatted files automatically
67
+
68
+ ### How to use the copied hook
69
+
70
+ After installation, run this command in your project root to use the copied hook:
71
+
72
+ ```bash
73
+ # Using default .git-hooks directory
74
+ git config core.hooksPath .git-hooks
75
+ ```
76
+
77
+ ```bash
78
+ # Or if you customized HOOKS_DIR in .env:
79
+ git config core.hooksPath your-custom-directory
80
+ ```
81
+
82
+ **Pro tip**: The installation script automatically configures Git for you, so this step is usually not needed!
83
+
84
+ ### Hook behavior
85
+
86
+ - ✅ **Safe**: Only copies if `{HOOKS_DIR}/pre-commit` doesn't exist (won't overwrite)
87
+ - ✅ **Smart**: Only installs when added as a dependency, not during development
88
+ - ✅ **Executable**: Automatically sets proper permissions (`chmod +x`)
89
+ - ✅ **Configurable**: Hooks directory can be customized via `.env` file
90
+ - ✅ **Auto-configured**: Git hooks path is set automatically during installation
91
+
92
+ ### Supported formats in .env
93
+
94
+ ```bash
95
+ # All these formats are valid:
96
+ HOOKS_DIR=.git-hooks # Basic format
97
+ HOOKS_DIR=".git-hooks" # With double quotes
98
+ HOOKS_DIR='.git-hooks' # With single quotes
99
+ HOOKS_DIR=custom-hooks-dir # Custom directory name
100
+ HOOKS_DIR="hooks with spaces" # Directories with spaces
101
+ ```
102
+
103
+ **Note**: If the `.env` file doesn't exist or has errors, the default `.git-hooks` directory is used automatically.
104
+
105
+ ### Custom implementation
106
+
107
+ If you prefer a custom implementation, you can create your own pre-commit hook using this example (adjust the hooks directory as needed):
108
+
109
+ ```bash
110
+ #!/bin/sh
111
+ # Replace .git-hooks with your HOOKS_DIR if customized
112
+
113
+ if git ls-files -u | grep -q .; then
114
+ echo "⚠️ Merge in progress. Skipping Prettier to avoid issues."
115
+ exit 0
116
+ fi
117
+
118
+ npm run prettier-staged
119
+
120
+ STAGED_FILES=$(git diff --name-only --cached --diff-filter=ACM | grep -E '\.(html|ts|scss|css|json)$')
121
+
122
+ if [ -n "$STAGED_FILES" ]; then
123
+ echo "$STAGED_FILES" | xargs git add
124
+ fi
125
+ ```
126
+
127
+ ### Quick start example
128
+
129
+ 1. **Install the package**:
130
+
131
+ ```bash
132
+ npm install -D @iamandersonp/prettier-staged
133
+ ```
134
+
135
+ 2. **Optional: Configure hooks directory**:
136
+
137
+ ```bash
138
+ echo "HOOKS_DIR=.git-hooks" > .env
139
+ ```
140
+
141
+ 3. **The hook is automatically set up!** No additional configuration needed.
142
+
143
+ 4. **Optional: Verify the setup**:
144
+
145
+ ```bash
146
+ git config core.hooksPath # Should show your hooks directory
147
+ ls -la .git-hooks/ # Should show the pre-commit hook
148
+ ```
149
+
150
+ ## Testing
151
+
152
+ This project includes comprehensive unit tests with Jest. The tests cover all the main functionality and edge cases.
153
+
154
+ ### Available test commands
155
+
156
+ ```bash
157
+ # Run all tests
158
+ npm test
159
+
160
+ # Run tests in watch mode (automatically re-run on file changes)
161
+ npm run test:watch
162
+
163
+ # Run tests with coverage report
164
+ npm run test:coverage
165
+ ```
166
+
167
+ ### Test coverage
168
+
169
+ Current test coverage: **>95%** including:
170
+
171
+ - ✅ Successful file formatting scenarios
172
+ - ✅ No files to format scenarios
173
+ - ✅ Error handling (Prettier not found, syntax errors, general errors)
174
+ - ✅ Edge cases (files with spaces, whitespace trimming, all supported extensions)
175
+
176
+ The tests use mocks to simulate Git commands and Prettier execution without running actual commands, making tests fast and reliable.
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ extends: ['@commitlint/config-conventional']
3
+ };
package/jest.config.js ADDED
@@ -0,0 +1,38 @@
1
+ module.exports = {
2
+ // Configuración Jest para Node.js CLI tool
3
+ testEnvironment: 'node',
4
+
5
+ // Patrones de archivos de test
6
+ testMatch: ['**/tests/**/*.test.js', '**/tests/**/*.spec.js', '**/__tests__/**/*.js'],
7
+
8
+ // Configuración de coverage
9
+ collectCoverageFrom: ['src/**/*.js', '!**/node_modules/**', '!**/tests/**'],
10
+
11
+ // Threshold mínimo de coverage
12
+ coverageThreshold: {
13
+ global: {
14
+ branches: 85,
15
+ functions: 85,
16
+ lines: 85,
17
+ statements: 85
18
+ }
19
+ },
20
+
21
+ // Formato de reportes de coverage
22
+ coverageReporters: ['text', 'html', 'lcov'],
23
+
24
+ // Directorio para reportes de coverage
25
+ coverageDirectory: 'coverage',
26
+
27
+ // Limpiar mocks automáticamente después de cada test
28
+ clearMocks: true,
29
+
30
+ // Restaurar mocks automáticamente después de cada test
31
+ restoreMocks: true,
32
+
33
+ // Configuración adicional para mejor output
34
+ verbose: true,
35
+
36
+ // Configuración de timeout para tests
37
+ testTimeout: 10000
38
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@iamandersonp/prettier-staged",
3
+ "version": "0.2.0",
4
+ "main": "src/index.js",
5
+ "bin": {
6
+ "prettier-staged": "src/index.js"
7
+ },
8
+ "scripts": {
9
+ "test": "jest",
10
+ "test:watch": "jest --watch",
11
+ "test:coverage": "jest --coverage",
12
+ "prettier-staged": "src/index.js",
13
+ "release": "standard-version",
14
+ "setup:git-hooks": "node src/install-hooks.js",
15
+ "release:minor": "standard-version --release-as minor",
16
+ "release:patch": "standard-version --release-as patch",
17
+ "release:major": "standard-version --release-as major",
18
+ "postinstall": "npm run setup:git-hooks"
19
+ },
20
+ "keywords": [
21
+ "prettier",
22
+ "staged",
23
+ "git",
24
+ "hooks"
25
+ ],
26
+ "author": "Anderson Penaloza <info@iamanderson.dev>",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/iamandersonp/prettier-staged.git"
30
+ },
31
+ "homepage": "https://github.com/iamandersonp/prettier-staged.git",
32
+ "license": "GPL-3.0-only",
33
+ "description": "An utility to auto format staged files using Prettier",
34
+ "readme": "README.md",
35
+ "dependencies": {
36
+ "prettier": "^3.8.1"
37
+ },
38
+ "devDependencies": {
39
+ "@commitlint/cli": "^20.5.0",
40
+ "@commitlint/config-conventional": "^20.5.0",
41
+ "jest": "^30.3.0",
42
+ "standard-version": "^9.5.0"
43
+ }
44
+ }
package/src/index.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('node:child_process');
4
+
5
+ const extensions = /\.(html|ts|scss|css|json)$/;
6
+
7
+ function runPrettierStaged() {
8
+ try {
9
+ const output = execSync('git diff --name-only --cached --diff-filter=ACM', {
10
+ encoding: 'utf-8'
11
+ });
12
+
13
+ const files = output
14
+ .split('\n')
15
+ .map((file) => file.trim())
16
+ .filter((file) => extensions.test(file));
17
+
18
+ if (files.length > 0) {
19
+ console.log('🧼 Formatting staged files with Prettier:');
20
+ execSync('npx prettier --write ' + files.join(' '), { stdio: 'inherit' });
21
+
22
+ // Re-staging
23
+ execSync(`git add ${files.join(' ')}`);
24
+ } else {
25
+ console.log('✅ No staged files matching for formatting.');
26
+ }
27
+ } catch (error) {
28
+ if (error.status === 127) {
29
+ console.error("❌ Error: Prettier not found. Make sure it's installed:", error.message);
30
+ } else if (error.status === 2) {
31
+ console.error('❌ Error: Prettier found syntax errors:', error.message);
32
+ } else {
33
+ console.error('❌ Error running Prettier:', error.message);
34
+ }
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ // Only run if this file is executed directly (not required as a module)
40
+ if (require.main === module) {
41
+ runPrettierStaged();
42
+ }
43
+
44
+ module.exports = { runPrettierStaged };
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ /**
7
+ * Lee el archivo .env para obtener HOOKS_DIR, retorna el valor por defecto si no existe
8
+ */
9
+ function getHooksDirFromEnv() {
10
+ const defaultHooksDir = '.git-hooks';
11
+
12
+ try {
13
+ const envPath = path.join(process.cwd(), '.env');
14
+
15
+ if (!fs.existsSync(envPath)) {
16
+ return defaultHooksDir;
17
+ }
18
+
19
+ const envContent = fs.readFileSync(envPath, 'utf8');
20
+ const lines = envContent.split('\n');
21
+
22
+ for (const line of lines) {
23
+ const trimmedLine = line.trim();
24
+ if (trimmedLine.startsWith('HOOKS_DIR=')) {
25
+ const value = trimmedLine.substring('HOOKS_DIR='.length).trim();
26
+ // Remover comillas si existen (simples o dobles)
27
+ let cleanValue = value
28
+ .replace(/^['"]/, '') // Remover comilla de inicio
29
+ .replace(/['"]$/, ''); // Remover comilla de final
30
+ return cleanValue || defaultHooksDir;
31
+ }
32
+ }
33
+
34
+ return defaultHooksDir;
35
+ } catch (error) {
36
+ // Si hay cualquier error leyendo el archivo, usar valor por defecto
37
+ console.warn('Warning: Could not read .env file, using default HOOKS_DIR:', error.message);
38
+ return defaultHooksDir;
39
+ }
40
+ }
41
+
42
+ // Constante para el directorio de hooks (lee desde .env o usa valor por defecto)
43
+ const HOOKS_DIR = getHooksDirFromEnv();
44
+
45
+ /**
46
+ * Detects if the package is being installed as a dependency in another project
47
+ * vs being installed during local development
48
+ */
49
+ function isExternalInstallation() {
50
+ const initCwd = process.env.INIT_CWD;
51
+ const currentCwd = process.cwd();
52
+
53
+ // If INIT_CWD is different from current working directory,
54
+ // we're being installed as a dependency
55
+ return !!(initCwd && initCwd !== currentCwd);
56
+ }
57
+
58
+ /**
59
+ * Gets the target project directory (where the package is being installed)
60
+ */
61
+ function getTargetProjectDir() {
62
+ // INIT_CWD contains the directory where npm install was run
63
+ return process.env.INIT_CWD || process.cwd();
64
+ }
65
+
66
+ /**
67
+ * Copies the pre-commit hook to the target project's .git-hooks directory
68
+ * if it doesn't already exist
69
+ */
70
+ function copyPreCommitHook() {
71
+ try {
72
+ const targetProjectDir = getTargetProjectDir();
73
+ const targetHooksDir = path.join(targetProjectDir, HOOKS_DIR);
74
+ const targetPreCommitPath = path.join(targetHooksDir, 'pre-commit');
75
+
76
+ // Check if pre-commit already exists
77
+ if (fs.existsSync(targetPreCommitPath)) {
78
+ console.log(`✅ ${HOOKS_DIR}/pre-commit already exists, skipping copy`);
79
+ return;
80
+ }
81
+
82
+ // Get source pre-commit path
83
+ const sourcePreCommitPath = path.join(__dirname, '..', '.git-hooks', 'pre-commit-sample');
84
+
85
+ if (!fs.existsSync(sourcePreCommitPath)) {
86
+ console.warn('⚠️ Source pre-commit hook not found, skipping copy');
87
+ return;
88
+ }
89
+
90
+ // Ensure target directory exists
91
+ if (!fs.existsSync(targetHooksDir)) {
92
+ fs.mkdirSync(targetHooksDir, { recursive: true });
93
+ console.log(`📁 Created ${HOOKS_DIR} directory`);
94
+ }
95
+
96
+ // Copy the file
97
+ fs.copyFileSync(sourcePreCommitPath, targetPreCommitPath);
98
+
99
+ // Make it executable
100
+ fs.chmodSync(targetPreCommitPath, 0o755);
101
+
102
+ console.log(`✅ Copied pre-commit hook to ${HOOKS_DIR}/pre-commit`);
103
+ console.log(`💡 To use it, run: git config core.hooksPath ${HOOKS_DIR}`);
104
+ } catch (error) {
105
+ // Don't fail installation if hook copy fails
106
+ console.warn('⚠️ Failed to copy pre-commit hook:', error.message);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Sets up git hooks in the library's own .git-hooks directory
112
+ * (existing functionality)
113
+ */
114
+ function setupLibraryGitHooks() {
115
+ const { execSync } = require('node:child_process');
116
+
117
+ try {
118
+ // Configure git to use .git-hooks directory
119
+ execSync(`git config core.hooksPath ${HOOKS_DIR}`, { stdio: 'ignore' });
120
+ console.log(`✅ Configured git to use ${HOOKS_DIR} directory`);
121
+
122
+ // Make hooks executable
123
+ execSync(`chmod +x ./${HOOKS_DIR}/*`, { stdio: 'ignore' });
124
+ console.log('✅ Made git hooks executable');
125
+ } catch (error) {
126
+ // Silently fail if not in a git repository or permissions issue
127
+ console.warn('⚠️ Could not setup library git hooks:', error.message);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Main installation function
133
+ */
134
+ function installHooks() {
135
+ console.log('🔧 Setting up prettier-staged hooks...');
136
+
137
+ // Always setup hooks for the library itself
138
+ setupLibraryGitHooks();
139
+
140
+ // Only copy to target project if this is an external installation
141
+ if (isExternalInstallation()) {
142
+ console.log('📦 Installing as dependency, copying pre-commit hook...');
143
+ copyPreCommitHook();
144
+ } else {
145
+ console.log('🏠 Running in development mode, skipping hook copy');
146
+ }
147
+
148
+ console.log('✨ Setup complete!');
149
+ }
150
+
151
+ // Export for testing
152
+ module.exports = {
153
+ isExternalInstallation,
154
+ getTargetProjectDir,
155
+ copyPreCommitHook,
156
+ setupLibraryGitHooks,
157
+ installHooks,
158
+ getHooksDirFromEnv,
159
+ HOOKS_DIR
160
+ };
161
+
162
+ // Run if called directly
163
+ if (require.main === module) {
164
+ installHooks();
165
+ }
@@ -0,0 +1,233 @@
1
+ const { execSync } = require('node:child_process');
2
+
3
+ // Mock completo del módulo child_process
4
+ jest.mock('node:child_process', () => ({
5
+ execSync: jest.fn()
6
+ }));
7
+
8
+ const { runPrettierStaged } = require('../src/index.js');
9
+
10
+ describe('prettier-staged CLI', () => {
11
+ let consoleSpy, errorSpy, exitSpy;
12
+
13
+ beforeEach(() => {
14
+ // Reset all mocks
15
+ jest.clearAllMocks();
16
+
17
+ // Setup console spies
18
+ consoleSpy = jest.spyOn(console, 'log').mockImplementation();
19
+ errorSpy = jest.spyOn(console, 'error').mockImplementation();
20
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation();
21
+ });
22
+
23
+ afterEach(() => {
24
+ // Restore all spies
25
+ consoleSpy.mockRestore();
26
+ errorSpy.mockRestore();
27
+ exitSpy.mockRestore();
28
+ });
29
+
30
+ describe('Successful formatting', () => {
31
+ test('should format staged files with valid extensions', () => {
32
+ // Mock git diff to return files with valid extensions
33
+ execSync
34
+ .mockReturnValueOnce('src/app.ts\nstyles/main.scss\nconfig.json\n')
35
+ .mockReturnValueOnce(undefined) // prettier command
36
+ .mockReturnValueOnce(undefined); // git add command
37
+
38
+ // Execute the function
39
+ runPrettierStaged();
40
+
41
+ // Verify git diff was called
42
+ expect(execSync).toHaveBeenNthCalledWith(
43
+ 1,
44
+ 'git diff --name-only --cached --diff-filter=ACM',
45
+ { encoding: 'utf-8' }
46
+ );
47
+
48
+ // Verify prettier was called with correct files
49
+ expect(execSync).toHaveBeenNthCalledWith(
50
+ 2,
51
+ 'npx prettier --write src/app.ts styles/main.scss config.json',
52
+ { stdio: 'inherit' }
53
+ );
54
+
55
+ // Verify git add was called with formatted files
56
+ expect(execSync).toHaveBeenNthCalledWith(
57
+ 3,
58
+ 'git add src/app.ts styles/main.scss config.json'
59
+ );
60
+
61
+ // Verify success message
62
+ expect(consoleSpy).toHaveBeenCalledWith('🧼 Formatting staged files with Prettier:');
63
+ expect(exitSpy).not.toHaveBeenCalled();
64
+ });
65
+
66
+ test('should handle single file with valid extension', () => {
67
+ execSync
68
+ .mockReturnValueOnce('index.html\n')
69
+ .mockReturnValueOnce(undefined)
70
+ .mockReturnValueOnce(undefined);
71
+
72
+ runPrettierStaged();
73
+
74
+ expect(execSync).toHaveBeenNthCalledWith(2, 'npx prettier --write index.html', {
75
+ stdio: 'inherit'
76
+ });
77
+ expect(consoleSpy).toHaveBeenCalledWith('🧼 Formatting staged files with Prettier:');
78
+ });
79
+ });
80
+
81
+ describe('No files to format', () => {
82
+ test('should show message when no staged files', () => {
83
+ execSync.mockReturnValueOnce('\n'); // Empty git diff output
84
+
85
+ runPrettierStaged();
86
+
87
+ expect(execSync).toHaveBeenCalledTimes(1);
88
+ expect(consoleSpy).toHaveBeenCalledWith('✅ No staged files matching for formatting.');
89
+ expect(exitSpy).not.toHaveBeenCalled();
90
+ });
91
+
92
+ test('should show message when staged files have invalid extensions', () => {
93
+ execSync.mockReturnValueOnce('README.md\nscript.py\nimage.png\n');
94
+
95
+ runPrettierStaged();
96
+
97
+ expect(execSync).toHaveBeenCalledTimes(1);
98
+ expect(consoleSpy).toHaveBeenCalledWith('✅ No staged files matching for formatting.');
99
+ expect(exitSpy).not.toHaveBeenCalled();
100
+ });
101
+
102
+ test('should filter mixed files and format only valid ones', () => {
103
+ execSync
104
+ .mockReturnValueOnce('README.md\nsrc/app.ts\nimage.png\nstyle.css\n')
105
+ .mockReturnValueOnce(undefined)
106
+ .mockReturnValueOnce(undefined);
107
+
108
+ runPrettierStaged();
109
+
110
+ expect(execSync).toHaveBeenNthCalledWith(2, 'npx prettier --write src/app.ts style.css', {
111
+ stdio: 'inherit'
112
+ });
113
+ expect(consoleSpy).toHaveBeenCalledWith('🧼 Formatting staged files with Prettier:');
114
+ });
115
+ });
116
+
117
+ describe('Error handling', () => {
118
+ test('should handle Prettier not found error (status 127)', () => {
119
+ execSync.mockReturnValueOnce('src/app.ts\n');
120
+
121
+ const error = new Error('prettier: command not found');
122
+ error.status = 127;
123
+ execSync.mockImplementationOnce(() => {
124
+ throw error;
125
+ });
126
+
127
+ runPrettierStaged();
128
+
129
+ expect(errorSpy).toHaveBeenCalledWith(
130
+ "❌ Error: Prettier not found. Make sure it's installed:",
131
+ error.message
132
+ );
133
+ expect(exitSpy).toHaveBeenCalledWith(1);
134
+ });
135
+
136
+ test('should handle Prettier syntax errors (status 2)', () => {
137
+ execSync.mockReturnValueOnce('src/broken.ts\n');
138
+
139
+ const error = new Error('Syntax error in file');
140
+ error.status = 2;
141
+ execSync.mockImplementationOnce(() => {
142
+ throw error;
143
+ });
144
+
145
+ runPrettierStaged();
146
+
147
+ expect(errorSpy).toHaveBeenCalledWith(
148
+ '❌ Error: Prettier found syntax errors:',
149
+ error.message
150
+ );
151
+ expect(exitSpy).toHaveBeenCalledWith(1);
152
+ });
153
+
154
+ test('should handle general Prettier errors', () => {
155
+ execSync.mockReturnValueOnce('src/app.ts\n');
156
+
157
+ const error = new Error('General prettier error');
158
+ error.status = 1;
159
+ execSync.mockImplementationOnce(() => {
160
+ throw error;
161
+ });
162
+
163
+ runPrettierStaged();
164
+
165
+ expect(errorSpy).toHaveBeenCalledWith('❌ Error running Prettier:', error.message);
166
+ expect(exitSpy).toHaveBeenCalledWith(1);
167
+ });
168
+
169
+ test('should handle git command errors', () => {
170
+ const error = new Error('Git command failed');
171
+ error.status = 128;
172
+ execSync.mockImplementationOnce(() => {
173
+ throw error;
174
+ });
175
+
176
+ runPrettierStaged();
177
+
178
+ expect(errorSpy).toHaveBeenCalledWith('❌ Error running Prettier:', error.message);
179
+ expect(exitSpy).toHaveBeenCalledWith(1);
180
+ });
181
+ });
182
+
183
+ describe('Edge cases', () => {
184
+ test('should handle files with spaces in names', () => {
185
+ execSync
186
+ .mockReturnValueOnce('src/my file.ts\nother file.css\n')
187
+ .mockReturnValueOnce(undefined)
188
+ .mockReturnValueOnce(undefined);
189
+
190
+ runPrettierStaged();
191
+
192
+ expect(execSync).toHaveBeenNthCalledWith(
193
+ 2,
194
+ 'npx prettier --write src/my file.ts other file.css',
195
+ { stdio: 'inherit' }
196
+ );
197
+ });
198
+
199
+ test('should trim whitespace from file names', () => {
200
+ execSync
201
+ .mockReturnValueOnce(' src/app.ts \n style.css \n\n')
202
+ .mockReturnValueOnce(undefined)
203
+ .mockReturnValueOnce(undefined);
204
+
205
+ runPrettierStaged();
206
+
207
+ expect(execSync).toHaveBeenNthCalledWith(2, 'npx prettier --write src/app.ts style.css', {
208
+ stdio: 'inherit'
209
+ });
210
+ });
211
+
212
+ test('should handle all supported file extensions', () => {
213
+ const validFiles = [
214
+ 'template.html',
215
+ 'component.ts',
216
+ 'styles.scss',
217
+ 'reset.css',
218
+ 'package.json'
219
+ ];
220
+
221
+ execSync
222
+ .mockReturnValueOnce(validFiles.join('\n') + '\n')
223
+ .mockReturnValueOnce(undefined)
224
+ .mockReturnValueOnce(undefined);
225
+
226
+ runPrettierStaged();
227
+
228
+ expect(execSync).toHaveBeenNthCalledWith(2, `npx prettier --write ${validFiles.join(' ')}`, {
229
+ stdio: 'inherit'
230
+ });
231
+ });
232
+ });
233
+ });