preetier-staged 0.0.1 → 0.0.3

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.
@@ -8,7 +8,9 @@ fi
8
8
 
9
9
  find src -name .DS_Store -exec rm {} \;
10
10
 
11
- prettier-staged
11
+ npm run test:coverage
12
+
13
+ npm run prettier-staged
12
14
 
13
15
  STAGED_FILES=$(git diff --name-only --cached --diff-filter=ACM | grep -E '\.(html|ts|scss|css|json)$')
14
16
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.0.3](https://github.com/iamandersonp/preetier-staged/compare/v0.0.2...v0.0.3) (2026-04-01)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * :rocket: add preetier dependency ([c892b0d](https://github.com/iamandersonp/preetier-staged/commits/c892b0dc699bc07411907d70386114f84ecf3c3c))
11
+
12
+ ### [0.0.2](https://github.com/iamandersonp/preetier-staged/compare/v0.0.1...v0.0.2) (2026-04-01)
13
+
5
14
  ### 0.0.1 (2026-04-01)
6
15
 
7
16
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  An utitlty to auto format stagged files using prettier
4
4
 
5
- ## Instalation
5
+ ## Installation
6
6
 
7
7
  To use as a dev dependency
8
8
 
@@ -18,6 +18,12 @@ npm i -g preetier-staged
18
18
 
19
19
  ## Setup
20
20
 
21
+ Create a command on your package.json
22
+
23
+ ```json
24
+ "prettier-staged": "prettier-staged",
25
+ ```
26
+
21
27
  By default preetier-stagged will check stagged files, so it's recommended to set up the pre-commit git hook
22
28
 
23
29
  This is an example of a posible implementation. In this example we skip the execution of command if there is a merge in progress
@@ -27,11 +33,11 @@ This is an example of a posible implementation. In this example we skip the exec
27
33
  #
28
34
 
29
35
  if git ls-files -u | grep -q .; then
30
- echo "⚠️ Merge in progress with conflicts. Skipping Prettier to avoid issues."
36
+ echo "⚠️ Merge in progress. Skipping Prettier to avoid issues."
31
37
  exit 0
32
38
  fi
33
39
 
34
- prettier-staged
40
+ npm run prettier-staged
35
41
 
36
42
  STAGED_FILES=$(git diff --name-only --cached --diff-filter=ACM | grep -E '\.(html|ts|scss|css|json)$')
37
43
 
@@ -39,3 +45,31 @@ if [ -n "$STAGED_FILES" ]; then
39
45
  echo "$STAGED_FILES" | xargs git add
40
46
  fi
41
47
  ```
48
+
49
+ ## Testing
50
+
51
+ This project includes comprehensive unit tests with Jest. The tests cover all the main functionality and edge cases.
52
+
53
+ ### Available test commands
54
+
55
+ ```bash
56
+ # Run all tests
57
+ npm test
58
+
59
+ # Run tests in watch mode (automatically re-run on file changes)
60
+ npm run test:watch
61
+
62
+ # Run tests with coverage report
63
+ npm run test:coverage
64
+ ```
65
+
66
+ ### Test coverage
67
+
68
+ Current test coverage: **>95%** including:
69
+
70
+ - ✅ Successful file formatting scenarios
71
+ - ✅ No files to format scenarios
72
+ - ✅ Error handling (Prettier not found, syntax errors, general errors)
73
+ - ✅ Edge cases (files with spaces, whitespace trimming, all supported extensions)
74
+
75
+ The tests use mocks to simulate Git commands and Prettier execution without running actual commands, making tests fast and reliable.
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 CHANGED
@@ -1,28 +1,42 @@
1
1
  {
2
2
  "name": "preetier-staged",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "main": "src/index.js",
5
5
  "bin": {
6
6
  "prettier-staged": "src/index.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "echo \"Error: no test specified\" && exit 1",
9
+ "test": "jest",
10
+ "test:watch": "jest --watch",
11
+ "test:coverage": "jest --coverage",
10
12
  "prettier-staged": "src/index.js",
11
13
  "release": "standard-version",
12
14
  "setup:git-hooks": "git config core.hooksPath .git-hooks || true && chmod +x ./.git-hooks/*",
13
15
  "release:minor": "standard-version --release-as minor",
14
16
  "release:patch": "standard-version --release-as patch",
15
17
  "release:major": "standard-version --release-as major",
16
- "prepare": "npm run setup:git-hooks"
18
+ "postinstall": "npm run setup:git-hooks"
19
+ },
20
+ "keywords": [
21
+ "prettier",
22
+ "staged",
23
+ "git",
24
+ "hooks"
25
+ ],
26
+ "author": "Anderson Peñaloza <info@iamanderson.dev>",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/iamandersonp/preetier-staged.git"
30
+ },
31
+ "license": "GPL-3.0-only",
32
+ "description": "An utility to auto format staged files using Prettier",
33
+ "dependencies": {
34
+ "prettier": "^3.8.1"
17
35
  },
18
- "keywords": [],
19
- "author": "",
20
- "license": "ISC",
21
- "description": "",
22
36
  "devDependencies": {
23
37
  "@commitlint/cli": "^20.5.0",
24
38
  "@commitlint/config-conventional": "^20.5.0",
25
- "prettier": "^3.8.1",
39
+ "jest": "^30.3.0",
26
40
  "standard-version": "^9.5.0"
27
41
  }
28
42
  }
package/src/index.js CHANGED
@@ -4,32 +4,41 @@ const { execSync } = require('node:child_process');
4
4
 
5
5
  const extensions = /\.(html|ts|scss|css|json)$/;
6
6
 
7
- try {
8
- const output = execSync('git diff --name-only --cached --diff-filter=ACM', {
9
- encoding: 'utf-8'
10
- });
11
-
12
- const files = output
13
- .split('\n')
14
- .map((file) => file.trim())
15
- .filter((file) => extensions.test(file));
16
-
17
- if (files.length > 0) {
18
- console.log('🧼 Formatting staged files with Prettier:');
19
- execSync('npx prettier --write ' + files.join(' '), { stdio: 'inherit' });
20
-
21
- // Re-staging
22
- execSync(`git add ${files.join(' ')}`);
23
- } else {
24
- console.log('✅ No staged files matching for formatting.');
25
- }
26
- } catch (error) {
27
- if (error.status === 127) {
28
- console.error("❌ Error: Prettier not found. Make sure it's installed:", error.message);
29
- } else if (error.status === 2) {
30
- console.error('❌ Error: Prettier found syntax errors:', error.message);
31
- } else {
32
- console.error('❌ Error running Prettier:', error.message);
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);
33
36
  }
34
- process.exit(1);
35
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,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
+ });