ts-repo-utils 6.1.0 → 7.0.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 +153 -33
- package/dist/cmd/assert-repo-is-clean.mjs +1 -1
- package/dist/cmd/check-should-run-type-checks.mjs +1 -1
- package/dist/cmd/format-diff-from.mjs +29 -8
- package/dist/cmd/format-diff-from.mjs.map +1 -1
- package/dist/cmd/format-uncommitted.d.mts +3 -0
- package/dist/cmd/format-uncommitted.d.mts.map +1 -0
- package/dist/cmd/format-uncommitted.mjs +59 -0
- package/dist/cmd/format-uncommitted.mjs.map +1 -0
- package/dist/cmd/gen-index-ts.mjs +1 -1
- package/dist/functions/assert-repo-is-clean.d.mts.map +1 -1
- package/dist/functions/assert-repo-is-clean.mjs +30 -30
- package/dist/functions/assert-repo-is-clean.mjs.map +1 -1
- package/dist/functions/diff.d.mts +32 -2
- package/dist/functions/diff.d.mts.map +1 -1
- package/dist/functions/diff.mjs +47 -29
- package/dist/functions/diff.mjs.map +1 -1
- package/dist/functions/exec-async.d.mts +4 -4
- package/dist/functions/exec-async.d.mts.map +1 -1
- package/dist/functions/exec-async.mjs +5 -5
- package/dist/functions/exec-async.mjs.map +1 -1
- package/dist/functions/format.d.mts +20 -11
- package/dist/functions/format.d.mts.map +1 -1
- package/dist/functions/format.mjs +136 -110
- package/dist/functions/format.mjs.map +1 -1
- package/dist/functions/gen-index.d.mts +2 -1
- package/dist/functions/gen-index.d.mts.map +1 -1
- package/dist/functions/gen-index.mjs +10 -8
- package/dist/functions/gen-index.mjs.map +1 -1
- package/dist/functions/index.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +2 -2
- package/src/cmd/assert-repo-is-clean.mts +1 -1
- package/src/cmd/check-should-run-type-checks.mts +1 -1
- package/src/cmd/format-diff-from.mts +35 -9
- package/src/cmd/format-uncommitted.mts +67 -0
- package/src/cmd/gen-index-ts.mts +1 -1
- package/src/functions/assert-repo-is-clean.mts +43 -34
- package/src/functions/diff.mts +85 -32
- package/src/functions/diff.test.mts +569 -102
- package/src/functions/exec-async.mts +21 -29
- package/src/functions/exec-async.test.mts +77 -47
- package/src/functions/format.mts +222 -150
- package/src/functions/format.test.mts +625 -20
- package/src/functions/gen-index.mts +16 -10
- package/src/functions/workspace-utils/run-cmd-in-stages.test.mts +266 -0
- package/dist/cmd/format-untracked.d.mts +0 -3
- package/dist/cmd/format-untracked.d.mts.map +0 -1
- package/dist/cmd/format-untracked.mjs +0 -34
- package/dist/cmd/format-untracked.mjs.map +0 -1
- package/src/cmd/format-untracked.mts +0 -31
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import { Result } from 'ts-data-forge';
|
|
3
3
|
import '../node-global.mjs';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
getDiffFrom,
|
|
6
|
+
getModifiedFiles,
|
|
7
|
+
getStagedFiles,
|
|
8
|
+
getUntrackedFiles,
|
|
9
|
+
} from './diff.mjs';
|
|
10
|
+
import {
|
|
11
|
+
formatDiffFrom,
|
|
12
|
+
formatFiles,
|
|
13
|
+
formatFilesGlob,
|
|
14
|
+
formatUncommittedFiles,
|
|
15
|
+
} from './format.mjs';
|
|
6
16
|
|
|
7
17
|
vi.mock('./diff.mjs', () => ({
|
|
8
18
|
getDiffFrom: vi.fn(),
|
|
19
|
+
getModifiedFiles: vi.fn(),
|
|
20
|
+
getStagedFiles: vi.fn(),
|
|
9
21
|
getUntrackedFiles: vi.fn(),
|
|
10
22
|
}));
|
|
11
23
|
|
|
12
|
-
describe('
|
|
13
|
-
const testDir = path.join(
|
|
24
|
+
describe('formatFilesGlob', () => {
|
|
25
|
+
const testDir = path.join(
|
|
26
|
+
process.cwd(),
|
|
27
|
+
`test-format-files-${crypto.randomUUID()}`,
|
|
28
|
+
);
|
|
14
29
|
|
|
15
30
|
// Helper to create a test file with unformatted content
|
|
16
31
|
const createTestFile = async (
|
|
@@ -54,8 +69,8 @@ describe('formatFiles', () => {
|
|
|
54
69
|
await createTestFile('test.md', '# Test\n\nSome spaces');
|
|
55
70
|
|
|
56
71
|
// Format TypeScript files
|
|
57
|
-
const result = await
|
|
58
|
-
expect(result).toBe(
|
|
72
|
+
const result = await formatFilesGlob(`${testDir}/*.ts`, { silent: true });
|
|
73
|
+
expect(Result.isOk(result)).toBe(true);
|
|
59
74
|
|
|
60
75
|
// Check that files were formatted
|
|
61
76
|
const content1 = await readTestFile(file1);
|
|
@@ -86,10 +101,10 @@ describe('formatFiles', () => {
|
|
|
86
101
|
|
|
87
102
|
test('should return ok when no files match pattern', async () => {
|
|
88
103
|
vi.clearAllMocks();
|
|
89
|
-
const result = await
|
|
104
|
+
const result = await formatFilesGlob('/non-existent-path/*.ts', {
|
|
90
105
|
silent: true,
|
|
91
106
|
});
|
|
92
|
-
expect(result).toBe(
|
|
107
|
+
expect(Result.isOk(result)).toBe(true);
|
|
93
108
|
});
|
|
94
109
|
|
|
95
110
|
test('should handle nested directories with glob pattern', async () => {
|
|
@@ -107,10 +122,10 @@ describe('formatFiles', () => {
|
|
|
107
122
|
);
|
|
108
123
|
|
|
109
124
|
// Format with recursive glob
|
|
110
|
-
const result = await
|
|
125
|
+
const result = await formatFilesGlob(`${testDir}/**/*.ts`, {
|
|
111
126
|
silent: true,
|
|
112
127
|
});
|
|
113
|
-
expect(result).toBe(
|
|
128
|
+
expect(Result.isOk(result)).toBe(true);
|
|
114
129
|
|
|
115
130
|
// Check that nested file was formatted
|
|
116
131
|
const content = await readTestFile(nestedFile);
|
|
@@ -128,8 +143,11 @@ describe('formatFiles', () => {
|
|
|
128
143
|
});
|
|
129
144
|
});
|
|
130
145
|
|
|
131
|
-
describe('
|
|
132
|
-
const testDir = path.join(
|
|
146
|
+
describe('formatFiles', () => {
|
|
147
|
+
const testDir = path.join(
|
|
148
|
+
process.cwd(),
|
|
149
|
+
`test-format-files-list-${crypto.randomUUID()}`,
|
|
150
|
+
);
|
|
133
151
|
|
|
134
152
|
// Helper to create a test file with unformatted content
|
|
135
153
|
const createTestFile = async (
|
|
@@ -166,10 +184,10 @@ describe('formatFilesList', () => {
|
|
|
166
184
|
);
|
|
167
185
|
|
|
168
186
|
// Format the files
|
|
169
|
-
const result = await
|
|
187
|
+
const result = await formatFiles([file1, file2], {
|
|
170
188
|
silent: true,
|
|
171
189
|
});
|
|
172
|
-
expect(result).toBe(
|
|
190
|
+
expect(Result.isOk(result)).toBe(true);
|
|
173
191
|
|
|
174
192
|
// Check formatted content
|
|
175
193
|
const content1 = await readTestFile(file1);
|
|
@@ -194,15 +212,386 @@ describe('formatFilesList', () => {
|
|
|
194
212
|
|
|
195
213
|
test('should return ok for empty file list', async () => {
|
|
196
214
|
vi.clearAllMocks();
|
|
197
|
-
const result = await
|
|
215
|
+
const result = await formatFiles([], {
|
|
198
216
|
silent: true,
|
|
199
217
|
});
|
|
200
|
-
expect(result).toBe(
|
|
218
|
+
expect(Result.isOk(result)).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('formatUncommittedFiles', () => {
|
|
223
|
+
const testDir = path.join(
|
|
224
|
+
process.cwd(),
|
|
225
|
+
`test-format-uncommitted-${crypto.randomUUID()}`,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const createTestFile = async (
|
|
229
|
+
filename: string,
|
|
230
|
+
content: string,
|
|
231
|
+
): Promise<string> => {
|
|
232
|
+
const filePath = path.join(testDir, filename);
|
|
233
|
+
const dir = path.dirname(filePath);
|
|
234
|
+
await fs.mkdir(dir, { recursive: true });
|
|
235
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
236
|
+
return filePath;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const setupTest = async (): Promise<void> => {
|
|
240
|
+
vi.clearAllMocks();
|
|
241
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const cleanupTest = async (): Promise<void> => {
|
|
245
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
test('should format all uncommitted files by default', async () => {
|
|
249
|
+
await setupTest();
|
|
250
|
+
try {
|
|
251
|
+
const untrackedFiles = ['untracked1.ts', 'untracked2.ts'];
|
|
252
|
+
const modifiedFiles = ['modified1.ts', 'modified2.ts'];
|
|
253
|
+
const stagedFiles = ['staged1.ts', 'staged2.ts'];
|
|
254
|
+
|
|
255
|
+
// Create test files
|
|
256
|
+
const allFiles = [...untrackedFiles, ...modifiedFiles, ...stagedFiles];
|
|
257
|
+
const filePromises = allFiles.map((file) =>
|
|
258
|
+
createTestFile(file, 'const x=1'),
|
|
259
|
+
);
|
|
260
|
+
await Promise.all(filePromises);
|
|
261
|
+
|
|
262
|
+
// Mock git functions
|
|
263
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
264
|
+
Result.ok(untrackedFiles.map((f) => path.join(testDir, f))),
|
|
265
|
+
);
|
|
266
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(
|
|
267
|
+
Result.ok(modifiedFiles.map((f) => path.join(testDir, f))),
|
|
268
|
+
);
|
|
269
|
+
vi.mocked(getStagedFiles).mockResolvedValue(
|
|
270
|
+
Result.ok(stagedFiles.map((f) => path.join(testDir, f))),
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const result = await formatUncommittedFiles({ silent: true });
|
|
274
|
+
expect(Result.isOk(result)).toBe(true);
|
|
275
|
+
|
|
276
|
+
// Verify all git functions were called
|
|
277
|
+
expect(getUntrackedFiles).toHaveBeenCalledWith({ silent: true });
|
|
278
|
+
expect(getModifiedFiles).toHaveBeenCalledWith({ silent: true });
|
|
279
|
+
expect(getStagedFiles).toHaveBeenCalledWith({ silent: true });
|
|
280
|
+
|
|
281
|
+
// Verify files were formatted
|
|
282
|
+
const verifyPromises = allFiles.map(async (file) => {
|
|
283
|
+
const content = await fs.readFile(path.join(testDir, file), 'utf8');
|
|
284
|
+
expect(content).toBe('const x = 1;\n');
|
|
285
|
+
});
|
|
286
|
+
await Promise.all(verifyPromises);
|
|
287
|
+
} finally {
|
|
288
|
+
await cleanupTest();
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('should format only untracked files when specified', async () => {
|
|
293
|
+
await setupTest();
|
|
294
|
+
try {
|
|
295
|
+
const untrackedFiles = ['untracked.ts'] as const;
|
|
296
|
+
await createTestFile(untrackedFiles[0], 'const x=1');
|
|
297
|
+
|
|
298
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
299
|
+
Result.ok(untrackedFiles.map((f) => path.join(testDir, f))),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const result = await formatUncommittedFiles({
|
|
303
|
+
untracked: true,
|
|
304
|
+
modified: false,
|
|
305
|
+
staged: false,
|
|
306
|
+
silent: true,
|
|
307
|
+
});
|
|
308
|
+
expect(Result.isOk(result)).toBe(true);
|
|
309
|
+
|
|
310
|
+
expect(getUntrackedFiles).toHaveBeenCalledWith({ silent: true });
|
|
311
|
+
expect(getModifiedFiles).not.toHaveBeenCalled();
|
|
312
|
+
expect(getStagedFiles).not.toHaveBeenCalled();
|
|
313
|
+
} finally {
|
|
314
|
+
await cleanupTest();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('should format only modified files when specified', async () => {
|
|
319
|
+
await setupTest();
|
|
320
|
+
try {
|
|
321
|
+
const modifiedFiles = ['modified.ts'] as const;
|
|
322
|
+
await createTestFile(modifiedFiles[0], 'const x=1');
|
|
323
|
+
|
|
324
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(
|
|
325
|
+
Result.ok(modifiedFiles.map((f) => path.join(testDir, f))),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const result = await formatUncommittedFiles({
|
|
329
|
+
untracked: false,
|
|
330
|
+
modified: true,
|
|
331
|
+
staged: false,
|
|
332
|
+
silent: true,
|
|
333
|
+
});
|
|
334
|
+
expect(Result.isOk(result)).toBe(true);
|
|
335
|
+
|
|
336
|
+
expect(getUntrackedFiles).not.toHaveBeenCalled();
|
|
337
|
+
expect(getModifiedFiles).toHaveBeenCalledWith({ silent: true });
|
|
338
|
+
expect(getStagedFiles).not.toHaveBeenCalled();
|
|
339
|
+
} finally {
|
|
340
|
+
await cleanupTest();
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('should format only staged files when specified', async () => {
|
|
345
|
+
await setupTest();
|
|
346
|
+
try {
|
|
347
|
+
const stagedFiles = ['staged.ts'] as const;
|
|
348
|
+
await createTestFile(stagedFiles[0], 'const x=1');
|
|
349
|
+
|
|
350
|
+
vi.mocked(getStagedFiles).mockResolvedValue(
|
|
351
|
+
Result.ok(stagedFiles.map((f) => path.join(testDir, f))),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const result = await formatUncommittedFiles({
|
|
355
|
+
untracked: false,
|
|
356
|
+
modified: false,
|
|
357
|
+
staged: true,
|
|
358
|
+
silent: true,
|
|
359
|
+
});
|
|
360
|
+
expect(Result.isOk(result)).toBe(true);
|
|
361
|
+
|
|
362
|
+
expect(getUntrackedFiles).not.toHaveBeenCalled();
|
|
363
|
+
expect(getModifiedFiles).not.toHaveBeenCalled();
|
|
364
|
+
expect(getStagedFiles).toHaveBeenCalledWith({ silent: true });
|
|
365
|
+
} finally {
|
|
366
|
+
await cleanupTest();
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('should handle combinations of file types', async () => {
|
|
371
|
+
await setupTest();
|
|
372
|
+
try {
|
|
373
|
+
const untrackedFiles = ['untracked.ts'];
|
|
374
|
+
const stagedFiles = ['staged.ts'];
|
|
375
|
+
|
|
376
|
+
const allFiles = [...untrackedFiles, ...stagedFiles];
|
|
377
|
+
const filePromises = allFiles.map((file) =>
|
|
378
|
+
createTestFile(file, 'const x=1'),
|
|
379
|
+
);
|
|
380
|
+
await Promise.all(filePromises);
|
|
381
|
+
|
|
382
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
383
|
+
Result.ok(untrackedFiles.map((f) => path.join(testDir, f))),
|
|
384
|
+
);
|
|
385
|
+
vi.mocked(getStagedFiles).mockResolvedValue(
|
|
386
|
+
Result.ok(stagedFiles.map((f) => path.join(testDir, f))),
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const result = await formatUncommittedFiles({
|
|
390
|
+
untracked: true,
|
|
391
|
+
modified: false,
|
|
392
|
+
staged: true,
|
|
393
|
+
silent: true,
|
|
394
|
+
});
|
|
395
|
+
expect(Result.isOk(result)).toBe(true);
|
|
396
|
+
|
|
397
|
+
expect(getUntrackedFiles).toHaveBeenCalledWith({ silent: true });
|
|
398
|
+
expect(getModifiedFiles).not.toHaveBeenCalled();
|
|
399
|
+
expect(getStagedFiles).toHaveBeenCalledWith({ silent: true });
|
|
400
|
+
} finally {
|
|
401
|
+
await cleanupTest();
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test('should deduplicate files that appear in multiple categories', async () => {
|
|
406
|
+
await setupTest();
|
|
407
|
+
try {
|
|
408
|
+
const duplicateFile = path.join(testDir, 'duplicate.ts');
|
|
409
|
+
await createTestFile('duplicate.ts', 'const x=1');
|
|
410
|
+
|
|
411
|
+
// Mock the same file appearing in multiple categories
|
|
412
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
413
|
+
Result.ok([duplicateFile]),
|
|
414
|
+
);
|
|
415
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([duplicateFile]));
|
|
416
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([duplicateFile]));
|
|
417
|
+
|
|
418
|
+
const result = await formatUncommittedFiles({ silent: true });
|
|
419
|
+
expect(Result.isOk(result)).toBe(true);
|
|
420
|
+
|
|
421
|
+
// Verify file was formatted (only once despite appearing in all categories)
|
|
422
|
+
const content = await fs.readFile(duplicateFile, 'utf8');
|
|
423
|
+
expect(content).toBe('const x = 1;\n');
|
|
424
|
+
} finally {
|
|
425
|
+
await cleanupTest();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('should handle empty file lists', async () => {
|
|
430
|
+
await setupTest();
|
|
431
|
+
try {
|
|
432
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([]));
|
|
433
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
434
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
435
|
+
|
|
436
|
+
const result = await formatUncommittedFiles({ silent: true });
|
|
437
|
+
expect(Result.isOk(result)).toBe(true);
|
|
438
|
+
} finally {
|
|
439
|
+
await cleanupTest();
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('should return error when getUntrackedFiles fails', async () => {
|
|
444
|
+
await setupTest();
|
|
445
|
+
try {
|
|
446
|
+
const error = { message: 'Git error' };
|
|
447
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.err(error));
|
|
448
|
+
|
|
449
|
+
const result = await formatUncommittedFiles({
|
|
450
|
+
untracked: true,
|
|
451
|
+
modified: false,
|
|
452
|
+
staged: false,
|
|
453
|
+
silent: true,
|
|
454
|
+
});
|
|
455
|
+
expect(Result.isErr(result)).toBe(true);
|
|
456
|
+
if (Result.isErr(result)) {
|
|
457
|
+
expect(result.value).toStrictEqual(error);
|
|
458
|
+
}
|
|
459
|
+
} finally {
|
|
460
|
+
await cleanupTest();
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
test('should return error when getModifiedFiles fails', async () => {
|
|
465
|
+
await setupTest();
|
|
466
|
+
try {
|
|
467
|
+
const error = { message: 'Git error' };
|
|
468
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.err(error));
|
|
469
|
+
|
|
470
|
+
const result = await formatUncommittedFiles({
|
|
471
|
+
untracked: false,
|
|
472
|
+
modified: true,
|
|
473
|
+
staged: false,
|
|
474
|
+
silent: true,
|
|
475
|
+
});
|
|
476
|
+
expect(Result.isErr(result)).toBe(true);
|
|
477
|
+
if (Result.isErr(result)) {
|
|
478
|
+
expect(result.value).toStrictEqual(error);
|
|
479
|
+
}
|
|
480
|
+
} finally {
|
|
481
|
+
await cleanupTest();
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test('should return error when getStagedFiles fails', async () => {
|
|
486
|
+
await setupTest();
|
|
487
|
+
try {
|
|
488
|
+
const error = { message: 'Git error' };
|
|
489
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.err(error));
|
|
490
|
+
|
|
491
|
+
const result = await formatUncommittedFiles({
|
|
492
|
+
untracked: false,
|
|
493
|
+
modified: false,
|
|
494
|
+
staged: true,
|
|
495
|
+
silent: true,
|
|
496
|
+
});
|
|
497
|
+
expect(Result.isErr(result)).toBe(true);
|
|
498
|
+
if (Result.isErr(result)) {
|
|
499
|
+
expect(result.value).toStrictEqual(error);
|
|
500
|
+
}
|
|
501
|
+
} finally {
|
|
502
|
+
await cleanupTest();
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test('should respect silent option', async () => {
|
|
507
|
+
await setupTest();
|
|
508
|
+
try {
|
|
509
|
+
// Using vi.stubGlobal to avoid direct assignment
|
|
510
|
+
const consoleErrorStub = vi.fn();
|
|
511
|
+
vi.stubGlobal('console', {
|
|
512
|
+
...console,
|
|
513
|
+
error: consoleErrorStub,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([]));
|
|
517
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
518
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
519
|
+
|
|
520
|
+
await formatUncommittedFiles({ silent: false });
|
|
521
|
+
// With silent: false, console output may occur
|
|
522
|
+
|
|
523
|
+
await formatUncommittedFiles({ silent: true });
|
|
524
|
+
// With silent: true, console output should be suppressed
|
|
525
|
+
|
|
526
|
+
vi.unstubAllGlobals();
|
|
527
|
+
} finally {
|
|
528
|
+
await cleanupTest();
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('should format TypeScript files correctly', async () => {
|
|
533
|
+
await setupTest();
|
|
534
|
+
try {
|
|
535
|
+
const testFile = 'test.ts';
|
|
536
|
+
await createTestFile(
|
|
537
|
+
testFile,
|
|
538
|
+
dedent`
|
|
539
|
+
function test(){return"hello"}
|
|
540
|
+
const obj={a:1,b:2}
|
|
541
|
+
`,
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
545
|
+
Result.ok([path.join(testDir, testFile)]),
|
|
546
|
+
);
|
|
547
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
548
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
549
|
+
|
|
550
|
+
const result = await formatUncommittedFiles({ silent: true });
|
|
551
|
+
expect(Result.isOk(result)).toBe(true);
|
|
552
|
+
|
|
553
|
+
const content = await fs.readFile(path.join(testDir, testFile), 'utf8');
|
|
554
|
+
expect(content).toBe(
|
|
555
|
+
`${dedent`
|
|
556
|
+
function test() {
|
|
557
|
+
return 'hello';
|
|
558
|
+
}
|
|
559
|
+
const obj = { a: 1, b: 2 };
|
|
560
|
+
`}\n`,
|
|
561
|
+
);
|
|
562
|
+
} finally {
|
|
563
|
+
await cleanupTest();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('should handle non-formattable files gracefully', async () => {
|
|
568
|
+
await setupTest();
|
|
569
|
+
try {
|
|
570
|
+
const binaryFile = 'test.bin';
|
|
571
|
+
const binaryPath = path.join(testDir, binaryFile);
|
|
572
|
+
await createTestFile(
|
|
573
|
+
binaryFile,
|
|
574
|
+
Buffer.from([0x00, 0x01, 0x02]).toString(),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([binaryPath]));
|
|
578
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
579
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
580
|
+
|
|
581
|
+
const result = await formatUncommittedFiles({ silent: true });
|
|
582
|
+
// Should handle error gracefully
|
|
583
|
+
expect(Result.isOk(result) || Result.isErr(result)).toBe(true);
|
|
584
|
+
} finally {
|
|
585
|
+
await cleanupTest();
|
|
586
|
+
}
|
|
201
587
|
});
|
|
202
588
|
});
|
|
203
589
|
|
|
204
590
|
describe('formatDiffFrom', () => {
|
|
205
|
-
const testDir = path.join(
|
|
591
|
+
const testDir = path.join(
|
|
592
|
+
process.cwd(),
|
|
593
|
+
`test-format-diff-${crypto.randomUUID()}`,
|
|
594
|
+
);
|
|
206
595
|
|
|
207
596
|
const createTestFile = async (
|
|
208
597
|
filename: string,
|
|
@@ -232,9 +621,11 @@ describe('formatDiffFrom', () => {
|
|
|
232
621
|
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([file1]));
|
|
233
622
|
|
|
234
623
|
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([]));
|
|
624
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
625
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
235
626
|
|
|
236
627
|
const result = await formatDiffFrom('main', { silent: true });
|
|
237
|
-
expect(result).toBe(
|
|
628
|
+
expect(Result.isOk(result)).toBe(true);
|
|
238
629
|
|
|
239
630
|
// Check file was formatted
|
|
240
631
|
const content = await readTestFile(file1);
|
|
@@ -275,12 +666,14 @@ describe('formatDiffFrom', () => {
|
|
|
275
666
|
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
276
667
|
Result.ok([untrackedFile]),
|
|
277
668
|
);
|
|
669
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
670
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
278
671
|
|
|
279
672
|
const result = await formatDiffFrom('main', {
|
|
280
673
|
includeUntracked: true,
|
|
281
674
|
silent: true,
|
|
282
675
|
});
|
|
283
|
-
expect(result).toBe(
|
|
676
|
+
expect(Result.isOk(result)).toBe(true);
|
|
284
677
|
|
|
285
678
|
// Check both files were formatted
|
|
286
679
|
const diffContent = await readTestFile(diffFile);
|
|
@@ -319,12 +712,14 @@ describe('formatDiffFrom', () => {
|
|
|
319
712
|
// Mock both functions to return the same file
|
|
320
713
|
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([sharedFile]));
|
|
321
714
|
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([sharedFile]));
|
|
715
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
716
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
322
717
|
|
|
323
718
|
const result = await formatDiffFrom('main', {
|
|
324
719
|
includeUntracked: true,
|
|
325
720
|
silent: true,
|
|
326
721
|
});
|
|
327
|
-
expect(result).toBe(
|
|
722
|
+
expect(Result.isOk(result)).toBe(true);
|
|
328
723
|
|
|
329
724
|
// Verify both functions were called
|
|
330
725
|
expect(getDiffFrom).toHaveBeenCalledWith('main', { silent: true });
|
|
@@ -341,4 +736,214 @@ describe('formatDiffFrom', () => {
|
|
|
341
736
|
await fs.rm(testDir, { recursive: true, force: true });
|
|
342
737
|
}
|
|
343
738
|
});
|
|
739
|
+
|
|
740
|
+
test('should include both staged and untracked files by default', async () => {
|
|
741
|
+
vi.clearAllMocks();
|
|
742
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
const diffFile = await createTestFile(
|
|
746
|
+
'diff.ts',
|
|
747
|
+
dedent`
|
|
748
|
+
const diff=true
|
|
749
|
+
`,
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
const stagedFile = await createTestFile(
|
|
753
|
+
'staged.ts',
|
|
754
|
+
dedent`
|
|
755
|
+
const staged=true
|
|
756
|
+
`,
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
const untrackedFile = await createTestFile(
|
|
760
|
+
'untracked.ts',
|
|
761
|
+
dedent`
|
|
762
|
+
const untracked=true
|
|
763
|
+
`,
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
// Mock all functions
|
|
767
|
+
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([diffFile]));
|
|
768
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
769
|
+
Result.ok([untrackedFile]),
|
|
770
|
+
);
|
|
771
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
772
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([stagedFile]));
|
|
773
|
+
|
|
774
|
+
// Test default behavior (no options provided)
|
|
775
|
+
const result = await formatDiffFrom('main', { silent: true });
|
|
776
|
+
expect(Result.isOk(result)).toBe(true);
|
|
777
|
+
|
|
778
|
+
// Check all files were formatted
|
|
779
|
+
const diffContent = await readTestFile(diffFile);
|
|
780
|
+
expect(diffContent).toBe(
|
|
781
|
+
`${dedent`
|
|
782
|
+
const diff = true;
|
|
783
|
+
`}\n`,
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
const stagedContent = await readTestFile(stagedFile);
|
|
787
|
+
expect(stagedContent).toBe(
|
|
788
|
+
`${dedent`
|
|
789
|
+
const staged = true;
|
|
790
|
+
`}\n`,
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
const untrackedContent = await readTestFile(untrackedFile);
|
|
794
|
+
expect(untrackedContent).toBe(
|
|
795
|
+
`${dedent`
|
|
796
|
+
const untracked = true;
|
|
797
|
+
`}\n`,
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
// Verify all functions were called by default
|
|
801
|
+
expect(getDiffFrom).toHaveBeenCalledWith('main', { silent: true });
|
|
802
|
+
expect(getStagedFiles).toHaveBeenCalledWith({ silent: true });
|
|
803
|
+
expect(getUntrackedFiles).toHaveBeenCalledWith({ silent: true });
|
|
804
|
+
} finally {
|
|
805
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test('should include staged files when option is set', async () => {
|
|
810
|
+
vi.clearAllMocks();
|
|
811
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
812
|
+
|
|
813
|
+
try {
|
|
814
|
+
const diffFile = await createTestFile(
|
|
815
|
+
'diff.ts',
|
|
816
|
+
dedent`
|
|
817
|
+
const diff=true
|
|
818
|
+
`,
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
const stagedFile = await createTestFile(
|
|
822
|
+
'staged.ts',
|
|
823
|
+
dedent`
|
|
824
|
+
const staged=true
|
|
825
|
+
`,
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
// Mock all functions
|
|
829
|
+
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([diffFile]));
|
|
830
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([]));
|
|
831
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
832
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([stagedFile]));
|
|
833
|
+
|
|
834
|
+
const result = await formatDiffFrom('main', {
|
|
835
|
+
includeStaged: true,
|
|
836
|
+
includeUntracked: false,
|
|
837
|
+
includeModified: false,
|
|
838
|
+
silent: true,
|
|
839
|
+
});
|
|
840
|
+
expect(Result.isOk(result)).toBe(true);
|
|
841
|
+
|
|
842
|
+
// Check both files were formatted
|
|
843
|
+
const diffContent = await readTestFile(diffFile);
|
|
844
|
+
expect(diffContent).toBe(
|
|
845
|
+
`${dedent`
|
|
846
|
+
const diff = true;
|
|
847
|
+
`}\n`,
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
const stagedContent = await readTestFile(stagedFile);
|
|
851
|
+
expect(stagedContent).toBe(
|
|
852
|
+
`${dedent`
|
|
853
|
+
const staged = true;
|
|
854
|
+
`}\n`,
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
expect(getDiffFrom).toHaveBeenCalledWith('main', { silent: true });
|
|
858
|
+
expect(getStagedFiles).toHaveBeenCalledWith({ silent: true });
|
|
859
|
+
expect(getUntrackedFiles).not.toHaveBeenCalled();
|
|
860
|
+
} finally {
|
|
861
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
test('should deduplicate files when including both staged and untracked', async () => {
|
|
866
|
+
vi.clearAllMocks();
|
|
867
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
868
|
+
|
|
869
|
+
try {
|
|
870
|
+
const sharedFile = await createTestFile(
|
|
871
|
+
'shared.ts',
|
|
872
|
+
dedent`
|
|
873
|
+
const shared={value:1}
|
|
874
|
+
`,
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
// Mock all functions to return the same file
|
|
878
|
+
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([sharedFile]));
|
|
879
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([sharedFile]));
|
|
880
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
881
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([sharedFile]));
|
|
882
|
+
|
|
883
|
+
const result = await formatDiffFrom('main', {
|
|
884
|
+
includeUntracked: true,
|
|
885
|
+
includeStaged: true,
|
|
886
|
+
silent: true,
|
|
887
|
+
});
|
|
888
|
+
expect(Result.isOk(result)).toBe(true);
|
|
889
|
+
|
|
890
|
+
// Verify all functions were called
|
|
891
|
+
expect(getDiffFrom).toHaveBeenCalledWith('main', { silent: true });
|
|
892
|
+
expect(getUntrackedFiles).toHaveBeenCalledWith({ silent: true });
|
|
893
|
+
expect(getStagedFiles).toHaveBeenCalledWith({ silent: true });
|
|
894
|
+
|
|
895
|
+
// Check that the file was formatted (content should change)
|
|
896
|
+
const finalContent = await readTestFile(sharedFile);
|
|
897
|
+
expect(finalContent).toBe(
|
|
898
|
+
`${dedent`
|
|
899
|
+
const shared = { value: 1 };
|
|
900
|
+
`}\n`,
|
|
901
|
+
);
|
|
902
|
+
} finally {
|
|
903
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
test('should exclude staged files when option is set to false', async () => {
|
|
908
|
+
vi.clearAllMocks();
|
|
909
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
910
|
+
|
|
911
|
+
try {
|
|
912
|
+
const diffFile = await createTestFile(
|
|
913
|
+
'diff.ts',
|
|
914
|
+
dedent`
|
|
915
|
+
const diff=true
|
|
916
|
+
`,
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
// Mock functions - staged should not be called
|
|
920
|
+
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([diffFile]));
|
|
921
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([]));
|
|
922
|
+
vi.mocked(getModifiedFiles).mockResolvedValue(Result.ok([]));
|
|
923
|
+
vi.mocked(getStagedFiles).mockResolvedValue(Result.ok([]));
|
|
924
|
+
|
|
925
|
+
const result = await formatDiffFrom('main', {
|
|
926
|
+
includeStaged: false,
|
|
927
|
+
includeUntracked: false,
|
|
928
|
+
includeModified: false,
|
|
929
|
+
silent: true,
|
|
930
|
+
});
|
|
931
|
+
expect(Result.isOk(result)).toBe(true);
|
|
932
|
+
|
|
933
|
+
// Check only diff file was formatted
|
|
934
|
+
const diffContent = await readTestFile(diffFile);
|
|
935
|
+
expect(diffContent).toBe(
|
|
936
|
+
`${dedent`
|
|
937
|
+
const diff = true;
|
|
938
|
+
`}\n`,
|
|
939
|
+
);
|
|
940
|
+
|
|
941
|
+
expect(getDiffFrom).toHaveBeenCalledWith('main', { silent: true });
|
|
942
|
+
expect(getStagedFiles).not.toHaveBeenCalled();
|
|
943
|
+
expect(getUntrackedFiles).not.toHaveBeenCalled();
|
|
944
|
+
expect(getModifiedFiles).not.toHaveBeenCalled();
|
|
945
|
+
} finally {
|
|
946
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
947
|
+
}
|
|
948
|
+
});
|
|
344
949
|
});
|