@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/.editorconfig +13 -0
- package/.env.example +14 -0
- package/.git-hooks/commit-msg +25 -0
- package/.git-hooks/pre-commit +19 -0
- package/.git-hooks/pre-commit-sample +15 -0
- package/.git-hooks/prepare-commit-msg +42 -0
- package/.prettierrc +6 -0
- package/.versionrc.json +14 -0
- package/.vscode/extensions.json +17 -0
- package/.vscode/settings.json +6 -0
- package/CHANGELOG.md +49 -0
- package/LICENSE +674 -0
- package/README.md +176 -0
- package/commitlint.config.js +3 -0
- package/jest.config.js +38 -0
- package/package.json +44 -0
- package/src/index.js +44 -0
- package/src/install-hooks.js +165 -0
- package/tests/index.test.js +233 -0
- package/tests/install-hooks.test.js +295 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { execSync } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
// Mock del módulo fs y child_process
|
|
6
|
+
jest.mock('node:fs');
|
|
7
|
+
jest.mock('node:child_process');
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
isExternalInstallation,
|
|
11
|
+
getTargetProjectDir,
|
|
12
|
+
copyPreCommitHook,
|
|
13
|
+
setupLibraryGitHooks,
|
|
14
|
+
installHooks,
|
|
15
|
+
getHooksDirFromEnv,
|
|
16
|
+
HOOKS_DIR
|
|
17
|
+
} = require('../src/install-hooks.js');
|
|
18
|
+
|
|
19
|
+
describe('install-hooks', () => {
|
|
20
|
+
let consoleSpy, warnSpy;
|
|
21
|
+
const originalEnv = process.env;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// Reset all mocks
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
|
|
27
|
+
// Setup fs mocks explicitly
|
|
28
|
+
fs.existsSync = jest.fn();
|
|
29
|
+
fs.mkdirSync = jest.fn();
|
|
30
|
+
fs.copyFileSync = jest.fn();
|
|
31
|
+
fs.chmodSync = jest.fn();
|
|
32
|
+
fs.readFileSync = jest.fn();
|
|
33
|
+
|
|
34
|
+
// Setup child_process mocks explicitly
|
|
35
|
+
execSync.mockReset();
|
|
36
|
+
|
|
37
|
+
// Reset environment
|
|
38
|
+
process.env = { ...originalEnv };
|
|
39
|
+
delete process.env.INIT_CWD;
|
|
40
|
+
|
|
41
|
+
// Setup console spies
|
|
42
|
+
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
43
|
+
warnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
// Restore environment and spies
|
|
48
|
+
process.env = originalEnv;
|
|
49
|
+
consoleSpy.mockRestore();
|
|
50
|
+
warnSpy.mockRestore();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('getHooksDirFromEnv', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
// Reset process.cwd for these tests
|
|
56
|
+
jest.spyOn(process, 'cwd').mockReturnValue('/test/project');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
process.cwd.mockRestore();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return default value when .env file does not exist', () => {
|
|
64
|
+
fs.existsSync.mockReturnValue(false);
|
|
65
|
+
|
|
66
|
+
const result = getHooksDirFromEnv();
|
|
67
|
+
|
|
68
|
+
expect(result).toBe('.git-hooks');
|
|
69
|
+
expect(fs.existsSync).toHaveBeenCalledWith('/test/project/.env');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return value from .env file when HOOKS_DIR is set', () => {
|
|
73
|
+
fs.existsSync.mockReturnValue(true);
|
|
74
|
+
fs.readFileSync.mockReturnValue('NODE_ENV=test\nHOOKS_DIR=custom-hooks\nOTHER=value');
|
|
75
|
+
|
|
76
|
+
const result = getHooksDirFromEnv();
|
|
77
|
+
|
|
78
|
+
expect(result).toBe('custom-hooks');
|
|
79
|
+
expect(fs.readFileSync).toHaveBeenCalledWith('/test/project/.env', 'utf8');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should return default value when .env exists but HOOKS_DIR is not set', () => {
|
|
83
|
+
fs.existsSync.mockReturnValue(true);
|
|
84
|
+
fs.readFileSync.mockReturnValue('NODE_ENV=test\nOTHER=value');
|
|
85
|
+
|
|
86
|
+
const result = getHooksDirFromEnv();
|
|
87
|
+
|
|
88
|
+
expect(result).toBe('.git-hooks');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should remove quotes from HOOKS_DIR value', () => {
|
|
92
|
+
fs.existsSync.mockReturnValue(true);
|
|
93
|
+
fs.readFileSync.mockReturnValue('HOOKS_DIR="quoted-hooks"');
|
|
94
|
+
|
|
95
|
+
const result = getHooksDirFromEnv();
|
|
96
|
+
|
|
97
|
+
expect(result).toBe('quoted-hooks');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should remove single quotes from HOOKS_DIR value', () => {
|
|
101
|
+
fs.existsSync.mockReturnValue(true);
|
|
102
|
+
fs.readFileSync.mockReturnValue("HOOKS_DIR='single-quoted'");
|
|
103
|
+
|
|
104
|
+
const result = getHooksDirFromEnv();
|
|
105
|
+
|
|
106
|
+
expect(result).toBe('single-quoted');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle file read errors gracefully', () => {
|
|
110
|
+
fs.existsSync.mockReturnValue(true);
|
|
111
|
+
fs.readFileSync.mockImplementation(() => {
|
|
112
|
+
throw new Error('Permission denied');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = getHooksDirFromEnv();
|
|
116
|
+
|
|
117
|
+
expect(result).toBe('.git-hooks');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should return default value when HOOKS_DIR is empty', () => {
|
|
121
|
+
fs.existsSync.mockReturnValue(true);
|
|
122
|
+
fs.readFileSync.mockReturnValue('HOOKS_DIR=\nOTHER=value');
|
|
123
|
+
|
|
124
|
+
const result = getHooksDirFromEnv();
|
|
125
|
+
|
|
126
|
+
expect(result).toBe('.git-hooks');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('isExternalInstallation', () => {
|
|
131
|
+
it('should return false when INIT_CWD is not set', () => {
|
|
132
|
+
expect(isExternalInstallation()).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should return false when INIT_CWD equals current working directory', () => {
|
|
136
|
+
process.env.INIT_CWD = process.cwd();
|
|
137
|
+
expect(isExternalInstallation()).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return true when INIT_CWD differs from current working directory', () => {
|
|
141
|
+
process.env.INIT_CWD = '/different/path';
|
|
142
|
+
expect(isExternalInstallation()).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('getTargetProjectDir', () => {
|
|
147
|
+
it('should return INIT_CWD when set', () => {
|
|
148
|
+
const targetDir = '/target/project';
|
|
149
|
+
process.env.INIT_CWD = targetDir;
|
|
150
|
+
expect(getTargetProjectDir()).toBe(targetDir);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should return current working directory when INIT_CWD is not set', () => {
|
|
154
|
+
expect(getTargetProjectDir()).toBe(process.cwd());
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('copyPreCommitHook', () => {
|
|
159
|
+
const targetDir = '/target/project';
|
|
160
|
+
const targetHooksDir = path.join(targetDir, HOOKS_DIR);
|
|
161
|
+
const targetPreCommit = path.join(targetHooksDir, 'pre-commit');
|
|
162
|
+
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
process.env.INIT_CWD = targetDir;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should skip copy if pre-commit already exists', () => {
|
|
168
|
+
fs.existsSync.mockReturnValue(true);
|
|
169
|
+
|
|
170
|
+
copyPreCommitHook();
|
|
171
|
+
|
|
172
|
+
expect(fs.copyFileSync).not.toHaveBeenCalled();
|
|
173
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
174
|
+
`✅ ${HOOKS_DIR}/pre-commit already exists, skipping copy`
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should warn if source pre-commit does not exist', () => {
|
|
179
|
+
fs.existsSync.mockImplementation((filePath) => {
|
|
180
|
+
if (filePath === targetPreCommit) return false;
|
|
181
|
+
if (filePath.includes('.git-hooks/pre-commit-sample')) return false;
|
|
182
|
+
return true;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
copyPreCommitHook();
|
|
186
|
+
|
|
187
|
+
expect(fs.copyFileSync).not.toHaveBeenCalled();
|
|
188
|
+
expect(warnSpy).toHaveBeenCalledWith('⚠️ Source pre-commit hook not found, skipping copy');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should create directory and copy file when conditions are met', () => {
|
|
192
|
+
// Mock file existence checks
|
|
193
|
+
fs.existsSync.mockImplementation((filePath) => {
|
|
194
|
+
if (filePath === targetPreCommit) return false; // Target doesn't exist
|
|
195
|
+
if (filePath === targetHooksDir) return false; // Directory doesn't exist
|
|
196
|
+
if (filePath.includes('.git-hooks/pre-commit-sample')) return true; // Source exists
|
|
197
|
+
return false;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
copyPreCommitHook();
|
|
201
|
+
|
|
202
|
+
expect(fs.mkdirSync).toHaveBeenCalledWith(targetHooksDir, { recursive: true });
|
|
203
|
+
expect(fs.copyFileSync).toHaveBeenCalled();
|
|
204
|
+
expect(fs.chmodSync).toHaveBeenCalledWith(targetPreCommit, 0o755);
|
|
205
|
+
expect(consoleSpy).toHaveBeenCalledWith(`📁 Created ${HOOKS_DIR} directory`);
|
|
206
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
207
|
+
`✅ Copied pre-commit hook to ${HOOKS_DIR}/pre-commit`
|
|
208
|
+
);
|
|
209
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
210
|
+
`💡 To use it, run: git config core.hooksPath ${HOOKS_DIR}`
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle copy errors gracefully', () => {
|
|
215
|
+
fs.existsSync.mockImplementation((filePath) => {
|
|
216
|
+
if (filePath === targetPreCommit) return false;
|
|
217
|
+
if (filePath.includes('.git-hooks/pre-commit-sample')) return true;
|
|
218
|
+
return false;
|
|
219
|
+
});
|
|
220
|
+
fs.copyFileSync.mockImplementation(() => {
|
|
221
|
+
throw new Error('Permission denied');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
copyPreCommitHook();
|
|
225
|
+
|
|
226
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
227
|
+
'⚠️ Failed to copy pre-commit hook:',
|
|
228
|
+
'Permission denied'
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('setupLibraryGitHooks', () => {
|
|
234
|
+
it('should configure git hooks successfully', () => {
|
|
235
|
+
execSync.mockImplementation(() => {});
|
|
236
|
+
|
|
237
|
+
setupLibraryGitHooks();
|
|
238
|
+
|
|
239
|
+
expect(execSync).toHaveBeenCalledWith(`git config core.hooksPath ${HOOKS_DIR}`, {
|
|
240
|
+
stdio: 'ignore'
|
|
241
|
+
});
|
|
242
|
+
expect(execSync).toHaveBeenCalledWith(`chmod +x ./${HOOKS_DIR}/*`, { stdio: 'ignore' });
|
|
243
|
+
expect(consoleSpy).toHaveBeenCalledWith(`✅ Configured git to use ${HOOKS_DIR} directory`);
|
|
244
|
+
expect(consoleSpy).toHaveBeenCalledWith('✅ Made git hooks executable');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should handle git configuration errors gracefully', () => {
|
|
248
|
+
execSync.mockImplementation((cmd) => {
|
|
249
|
+
if (cmd.includes('git config')) {
|
|
250
|
+
throw new Error('Not a git repository');
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
setupLibraryGitHooks();
|
|
255
|
+
|
|
256
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
257
|
+
'⚠️ Could not setup library git hooks:',
|
|
258
|
+
'Not a git repository'
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('installHooks', () => {
|
|
264
|
+
it('should setup library hooks and skip copy in development mode', () => {
|
|
265
|
+
process.env.INIT_CWD = process.cwd(); // Same as current directory
|
|
266
|
+
|
|
267
|
+
// Mock successful library setup
|
|
268
|
+
execSync.mockImplementation(() => {});
|
|
269
|
+
|
|
270
|
+
installHooks();
|
|
271
|
+
|
|
272
|
+
expect(consoleSpy).toHaveBeenCalledWith('🔧 Setting up prettier-staged hooks...');
|
|
273
|
+
expect(consoleSpy).toHaveBeenCalledWith('🏠 Running in development mode, skipping hook copy');
|
|
274
|
+
expect(consoleSpy).toHaveBeenCalledWith('✨ Setup complete!');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should setup library hooks and copy to target project in external installation', () => {
|
|
278
|
+
process.env.INIT_CWD = '/different/project';
|
|
279
|
+
|
|
280
|
+
// Mock successful operations
|
|
281
|
+
execSync.mockImplementation(() => {});
|
|
282
|
+
fs.existsSync.mockImplementation((filePath) => {
|
|
283
|
+
return !!filePath.includes('.git-hooks/pre-commit-sample'); // Target doesn't exist
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
installHooks();
|
|
287
|
+
|
|
288
|
+
expect(consoleSpy).toHaveBeenCalledWith('🔧 Setting up prettier-staged hooks...');
|
|
289
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
290
|
+
'📦 Installing as dependency, copying pre-commit hook...'
|
|
291
|
+
);
|
|
292
|
+
expect(consoleSpy).toHaveBeenCalledWith('✨ Setup complete!');
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|