@amirdaraee/namewise 0.5.2 → 0.5.4

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/index.js +0 -0
  3. package/package.json +2 -2
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -82
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -61
  6. package/.github/workflows/auto-release.yml +0 -78
  7. package/.github/workflows/build.yml +0 -55
  8. package/.github/workflows/publish.yml +0 -134
  9. package/.github/workflows/test.yml +0 -47
  10. package/eng.traineddata +0 -0
  11. package/src/cli/commands.ts +0 -64
  12. package/src/cli/rename.ts +0 -171
  13. package/src/index.ts +0 -54
  14. package/src/parsers/excel-parser.ts +0 -66
  15. package/src/parsers/factory.ts +0 -38
  16. package/src/parsers/pdf-parser.ts +0 -99
  17. package/src/parsers/text-parser.ts +0 -43
  18. package/src/parsers/word-parser.ts +0 -50
  19. package/src/services/ai-factory.ts +0 -39
  20. package/src/services/claude-service.ts +0 -119
  21. package/src/services/file-renamer.ts +0 -141
  22. package/src/services/lmstudio-service.ts +0 -161
  23. package/src/services/ollama-service.ts +0 -191
  24. package/src/services/openai-service.ts +0 -117
  25. package/src/types/index.ts +0 -76
  26. package/src/types/pdf-extraction.d.ts +0 -7
  27. package/src/utils/ai-prompts.ts +0 -76
  28. package/src/utils/file-templates.ts +0 -275
  29. package/src/utils/naming-conventions.ts +0 -67
  30. package/src/utils/pdf-to-image.ts +0 -137
  31. package/tests/data/console-test-1.txt +0 -1
  32. package/tests/data/console-test-2.txt +0 -1
  33. package/tests/data/console-test-long-filename-for-display-testing.txt +0 -1
  34. package/tests/data/empty-file.txt +0 -0
  35. package/tests/data/failure.txt +0 -1
  36. package/tests/data/file1.txt +0 -1
  37. package/tests/data/file2.txt +0 -1
  38. package/tests/data/much-longer-filename-to-test-clearing.txt +0 -1
  39. package/tests/data/sample-markdown.md +0 -9
  40. package/tests/data/sample-pdf.pdf +0 -0
  41. package/tests/data/sample-text.txt +0 -25
  42. package/tests/data/short.txt +0 -1
  43. package/tests/data/single-file.txt +0 -1
  44. package/tests/data/success.txt +0 -1
  45. package/tests/data/this-is-a-very-long-filename-that-should-be-truncated-for-better-display-purposes.txt +0 -1
  46. package/tests/data/very-long-filename-that-should-be-cleared-properly.txt +0 -1
  47. package/tests/data/x.txt +0 -1
  48. package/tests/integration/ai-prompting.test.ts +0 -386
  49. package/tests/integration/end-to-end.test.ts +0 -209
  50. package/tests/integration/person-name-extraction.test.ts +0 -440
  51. package/tests/integration/workflow.test.ts +0 -336
  52. package/tests/mocks/mock-ai-service.ts +0 -58
  53. package/tests/unit/cli/commands.test.ts +0 -169
  54. package/tests/unit/parsers/factory.test.ts +0 -100
  55. package/tests/unit/parsers/pdf-parser.test.ts +0 -63
  56. package/tests/unit/parsers/text-parser.test.ts +0 -85
  57. package/tests/unit/services/ai-factory.test.ts +0 -85
  58. package/tests/unit/services/claude-service.test.ts +0 -188
  59. package/tests/unit/services/file-renamer.test.ts +0 -514
  60. package/tests/unit/services/lmstudio-service.test.ts +0 -326
  61. package/tests/unit/services/ollama-service.test.ts +0 -264
  62. package/tests/unit/services/openai-service.test.ts +0 -196
  63. package/tests/unit/utils/ai-prompts.test.ts +0 -213
  64. package/tests/unit/utils/file-templates.test.ts +0 -199
  65. package/tests/unit/utils/naming-conventions.test.ts +0 -88
  66. package/tests/unit/utils/pdf-to-image.test.ts +0 -127
  67. package/tsconfig.json +0 -20
  68. package/vitest.config.ts +0 -30
@@ -1,514 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import { FileRenamer } from '../../../src/services/file-renamer.js';
5
- import { DocumentParserFactory } from '../../../src/parsers/factory.js';
6
- import { MockAIService } from '../../mocks/mock-ai-service.js';
7
- import { Config, FileInfo } from '../../../src/types/index.js';
8
-
9
- // Mock fs.rename to avoid actual file operations
10
- vi.mock('fs', async () => {
11
- const actual = await vi.importActual('fs');
12
- return {
13
- ...actual,
14
- promises: {
15
- ...actual.promises,
16
- rename: vi.fn(),
17
- access: vi.fn()
18
- }
19
- };
20
- });
21
-
22
- describe('FileRenamer', () => {
23
- let fileRenamer: FileRenamer;
24
- let mockAIService: MockAIService;
25
- let parserFactory: DocumentParserFactory;
26
- let config: Config;
27
- const testDataDir = path.join(process.cwd(), 'tests/data');
28
-
29
- beforeEach(() => {
30
- mockAIService = new MockAIService();
31
- parserFactory = new DocumentParserFactory();
32
- config = {
33
- aiProvider: 'claude',
34
- apiKey: 'test-key',
35
- maxFileSize: 10 * 1024 * 1024, // 10MB
36
- supportedExtensions: ['.txt', '.pdf', '.docx', '.xlsx'],
37
- dryRun: false,
38
- namingConvention: 'kebab-case',
39
- templateOptions: {
40
- category: 'general',
41
- personalName: undefined,
42
- dateFormat: 'none'
43
- }
44
- };
45
-
46
- fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
47
-
48
- // Reset mocks
49
- vi.clearAllMocks();
50
- mockAIService.resetCallCount();
51
- });
52
-
53
- afterEach(() => {
54
- vi.restoreAllMocks();
55
- });
56
-
57
- describe('renameFiles()', () => {
58
- it('should successfully rename files', async () => {
59
- const testFiles: FileInfo[] = [
60
- {
61
- path: path.join(testDataDir, 'sample-text.txt'),
62
- name: 'sample-text.txt',
63
- extension: '.txt',
64
- size: 1000
65
- }
66
- ];
67
-
68
- // Mock fs.access to simulate that new file doesn't exist
69
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
70
- vi.mocked(fs.rename).mockResolvedValue(undefined);
71
-
72
- const results = await fileRenamer.renameFiles(testFiles);
73
-
74
- expect(results).toHaveLength(1);
75
- expect(results[0].success).toBe(true);
76
- expect(results[0].originalPath).toBe(testFiles[0].path);
77
- expect(results[0].newPath).toContain('project-requirements-document.txt');
78
- expect(mockAIService.getCallCount()).toBe(1);
79
- expect(fs.rename).toHaveBeenCalledOnce();
80
- });
81
-
82
- it('should handle dry run mode', async () => {
83
- config.dryRun = true;
84
- fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
85
-
86
- const testFiles: FileInfo[] = [
87
- {
88
- path: path.join(testDataDir, 'sample-text.txt'),
89
- name: 'sample-text.txt',
90
- extension: '.txt',
91
- size: 1000
92
- }
93
- ];
94
-
95
- // Mock fs.access to simulate that new file doesn't exist
96
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
97
-
98
- const results = await fileRenamer.renameFiles(testFiles);
99
-
100
- expect(results).toHaveLength(1);
101
- expect(results[0].success).toBe(true);
102
- expect(mockAIService.getCallCount()).toBe(1);
103
- expect(fs.rename).not.toHaveBeenCalled(); // Should not rename in dry run
104
- });
105
-
106
- it('should handle file size limits', async () => {
107
- config.maxFileSize = 100; // Very small limit
108
- fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
109
-
110
- const testFiles: FileInfo[] = [
111
- {
112
- path: path.join(testDataDir, 'sample-text.txt'),
113
- name: 'sample-text.txt',
114
- extension: '.txt',
115
- size: 1000 // Exceeds limit
116
- }
117
- ];
118
-
119
- const results = await fileRenamer.renameFiles(testFiles);
120
-
121
- expect(results).toHaveLength(1);
122
- expect(results[0].success).toBe(false);
123
- expect(results[0].error).toContain('File size');
124
- expect(results[0].error).toContain('exceeds maximum');
125
- expect(mockAIService.getCallCount()).toBe(0); // Should not call AI
126
- expect(fs.rename).not.toHaveBeenCalled();
127
- });
128
-
129
- it('should handle unsupported file types', async () => {
130
- const testFiles: FileInfo[] = [
131
- {
132
- path: path.join(testDataDir, 'unsupported.xyz'),
133
- name: 'unsupported.xyz',
134
- extension: '.xyz',
135
- size: 1000
136
- }
137
- ];
138
-
139
- const results = await fileRenamer.renameFiles(testFiles);
140
-
141
- expect(results).toHaveLength(1);
142
- expect(results[0].success).toBe(false);
143
- expect(results[0].error).toContain('No parser available');
144
- expect(mockAIService.getCallCount()).toBe(0);
145
- expect(fs.rename).not.toHaveBeenCalled();
146
- });
147
-
148
- it('should handle AI service failures', async () => {
149
- mockAIService.setShouldFail(true);
150
-
151
- const testFiles: FileInfo[] = [
152
- {
153
- path: path.join(testDataDir, 'sample-text.txt'),
154
- name: 'sample-text.txt',
155
- extension: '.txt',
156
- size: 1000
157
- }
158
- ];
159
-
160
- const results = await fileRenamer.renameFiles(testFiles);
161
-
162
- expect(results).toHaveLength(1);
163
- expect(results[0].success).toBe(false);
164
- expect(results[0].error).toContain('Mock AI service failed');
165
- expect(mockAIService.getCallCount()).toBe(1);
166
- expect(fs.rename).not.toHaveBeenCalled();
167
- });
168
-
169
- it('should handle file conflicts', async () => {
170
- const testFiles: FileInfo[] = [
171
- {
172
- path: path.join(testDataDir, 'sample-text.txt'),
173
- name: 'sample-text.txt',
174
- extension: '.txt',
175
- size: 1000
176
- }
177
- ];
178
-
179
- // Mock fs.access to simulate that new file already exists
180
- vi.mocked(fs.access).mockResolvedValue(undefined);
181
-
182
- const results = await fileRenamer.renameFiles(testFiles);
183
-
184
- expect(results).toHaveLength(1);
185
- expect(results[0].success).toBe(false);
186
- expect(results[0].error).toContain('Target filename already exists');
187
- expect(mockAIService.getCallCount()).toBe(1);
188
- expect(fs.rename).not.toHaveBeenCalled();
189
- });
190
-
191
- it('should handle multiple files', async () => {
192
- const testFiles: FileInfo[] = [
193
- {
194
- path: path.join(testDataDir, 'sample-text.txt'),
195
- name: 'sample-text.txt',
196
- extension: '.txt',
197
- size: 1000
198
- },
199
- {
200
- path: path.join(testDataDir, 'sample-markdown.md'),
201
- name: 'sample-markdown.md',
202
- extension: '.md',
203
- size: 500
204
- }
205
- ];
206
-
207
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
208
- vi.mocked(fs.rename).mockResolvedValue(undefined);
209
-
210
- const results = await fileRenamer.renameFiles(testFiles);
211
-
212
- expect(results).toHaveLength(2);
213
- expect(results.every(r => r.success)).toBe(true);
214
- expect(mockAIService.getCallCount()).toBe(2);
215
- expect(fs.rename).toHaveBeenCalledTimes(2);
216
- });
217
-
218
- it('should handle empty file content', async () => {
219
- const testFiles: FileInfo[] = [
220
- {
221
- path: path.join(testDataDir, 'empty-file.txt'),
222
- name: 'empty-file.txt',
223
- extension: '.txt',
224
- size: 0
225
- }
226
- ];
227
-
228
- const results = await fileRenamer.renameFiles(testFiles);
229
-
230
- expect(results).toHaveLength(1);
231
- expect(results[0].success).toBe(false);
232
- expect(results[0].error).toContain('No content could be extracted');
233
- expect(mockAIService.getCallCount()).toBe(0);
234
- expect(fs.rename).not.toHaveBeenCalled();
235
- });
236
-
237
- it('should not rename if filename would be the same', async () => {
238
- // Set up mock to return a name that would result in the same filename
239
- mockAIService.setMockResponse('default', 'sample-text');
240
-
241
- const testFiles: FileInfo[] = [
242
- {
243
- path: path.join(testDataDir, 'sample-text.txt'),
244
- name: 'sample-text.txt',
245
- extension: '.txt',
246
- size: 1000
247
- }
248
- ];
249
-
250
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
251
-
252
- const results = await fileRenamer.renameFiles(testFiles);
253
-
254
- expect(results).toHaveLength(1);
255
- expect(results[0].success).toBe(true);
256
- expect(results[0].originalPath).toBe(results[0].newPath); // Same path
257
- expect(mockAIService.getCallCount()).toBe(1);
258
- expect(fs.rename).not.toHaveBeenCalled(); // No rename needed
259
- });
260
-
261
- it('should pass naming convention to AI service', async () => {
262
- config.namingConvention = 'snake_case';
263
- fileRenamer = new FileRenamer(parserFactory, mockAIService, config);
264
-
265
- // Spy on the generateFileName method
266
- const generateFileNameSpy = vi.spyOn(mockAIService, 'generateFileName');
267
-
268
- const testFiles: FileInfo[] = [
269
- {
270
- path: path.join(testDataDir, 'sample-text.txt'),
271
- name: 'sample-text.txt',
272
- extension: '.txt',
273
- size: 1000
274
- }
275
- ];
276
-
277
- // Mock fs.access to simulate that new file doesn't exist
278
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
279
-
280
- const results = await fileRenamer.renameFiles(testFiles);
281
-
282
- expect(results).toHaveLength(1);
283
- expect(results[0].success).toBe(true);
284
-
285
- // Verify AI service was called with the naming convention, category, and file info
286
- expect(generateFileNameSpy).toHaveBeenCalledWith(
287
- expect.any(String),
288
- 'sample-text.txt',
289
- 'snake_case',
290
- 'general', // Uses general template since that's the default (no auto-categorization)
291
- expect.objectContaining({
292
- name: 'sample-text.txt',
293
- extension: '.txt',
294
- documentMetadata: expect.any(Object)
295
- })
296
- );
297
- });
298
- });
299
-
300
- describe('Console Output', () => {
301
- let originalStdoutWrite: typeof process.stdout.write;
302
- let stdoutOutput: string[];
303
-
304
- beforeEach(() => {
305
- stdoutOutput = [];
306
- originalStdoutWrite = process.stdout.write;
307
-
308
- // Mock process.stdout.write to capture output
309
- process.stdout.write = vi.fn((chunk: any) => {
310
- if (typeof chunk === 'string') {
311
- stdoutOutput.push(chunk);
312
- }
313
- return true;
314
- }) as any;
315
- });
316
-
317
- afterEach(() => {
318
- process.stdout.write = originalStdoutWrite;
319
- });
320
-
321
- it('should display progress messages during processing', async () => {
322
- const testFiles: FileInfo[] = [
323
- {
324
- path: path.join(testDataDir, 'file1.txt'),
325
- name: 'file1.txt',
326
- extension: '.txt',
327
- size: 1000
328
- },
329
- {
330
- path: path.join(testDataDir, 'file2.txt'),
331
- name: 'file2.txt',
332
- extension: '.txt',
333
- size: 1000
334
- },
335
- {
336
- path: path.join(testDataDir, 'very-long-filename-that-should-be-cleared-properly.txt'),
337
- name: 'very-long-filename-that-should-be-cleared-properly.txt',
338
- extension: '.txt',
339
- size: 1000
340
- }
341
- ];
342
-
343
- // Mock fs.access to simulate that new file doesn't exist
344
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
345
- vi.mocked(fs.rename).mockResolvedValue(undefined);
346
-
347
- await fileRenamer.renameFiles(testFiles);
348
-
349
- // Check that progress messages were written
350
- const outputString = stdoutOutput.join('');
351
- expect(outputString).toContain('🔄 Processing [1/3] file1.txt');
352
- expect(outputString).toContain('🔄 Processing [2/3] file2.txt');
353
- expect(outputString).toContain('🔄 Processing [3/3] very-long-filename-that-should-be-cleared-prope...');
354
- });
355
-
356
- it('should properly clear previous progress lines', async () => {
357
- const testFiles: FileInfo[] = [
358
- {
359
- path: path.join(testDataDir, 'short.txt'),
360
- name: 'short.txt',
361
- extension: '.txt',
362
- size: 1000
363
- },
364
- {
365
- path: path.join(testDataDir, 'much-longer-filename-to-test-clearing.txt'),
366
- name: 'much-longer-filename-to-test-clearing.txt',
367
- extension: '.txt',
368
- size: 1000
369
- },
370
- {
371
- path: path.join(testDataDir, 'x.txt'),
372
- name: 'x.txt',
373
- extension: '.txt',
374
- size: 1000
375
- }
376
- ];
377
-
378
- // Mock fs.access to simulate that new file doesn't exist
379
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
380
- vi.mocked(fs.rename).mockResolvedValue(undefined);
381
-
382
- await fileRenamer.renameFiles(testFiles);
383
-
384
- // Should contain clearing sequences (spaces to overwrite previous content)
385
- const outputString = stdoutOutput.join('');
386
-
387
- // Should contain carriage returns and spaces for clearing
388
- expect(outputString).toContain('\r');
389
- expect(outputString).toMatch(/\s+/); // Should contain spaces for clearing
390
-
391
- // Final clear should happen at the end
392
- const lastOutputs = stdoutOutput.slice(-3);
393
- expect(lastOutputs.some(output => output.includes('\r') && output.includes(' '))).toBe(true);
394
- });
395
-
396
- it('should handle single file processing correctly', async () => {
397
- const testFiles: FileInfo[] = [
398
- {
399
- path: path.join(testDataDir, 'single-file.txt'),
400
- name: 'single-file.txt',
401
- extension: '.txt',
402
- size: 1000
403
- }
404
- ];
405
-
406
- // Mock fs.access to simulate that new file doesn't exist
407
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
408
- vi.mocked(fs.rename).mockResolvedValue(undefined);
409
-
410
- await fileRenamer.renameFiles(testFiles);
411
-
412
- const outputString = stdoutOutput.join('');
413
- expect(outputString).toContain('🔄 Processing [1/1] single-file.txt');
414
-
415
- // Should still clear the line at the end
416
- expect(outputString).toContain('\r');
417
- });
418
-
419
- it('should handle empty file list without console output', async () => {
420
- const testFiles: FileInfo[] = [];
421
-
422
- await fileRenamer.renameFiles(testFiles);
423
-
424
- // With no files, there should be minimal or no output
425
- expect(stdoutOutput.length).toBeLessThan(3);
426
- });
427
-
428
- it('should show completion message after processing', async () => {
429
- const testFiles: FileInfo[] = [
430
- {
431
- path: path.join(testDataDir, 'file1.txt'),
432
- name: 'file1.txt',
433
- extension: '.txt',
434
- size: 1000
435
- },
436
- {
437
- path: path.join(testDataDir, 'file2.txt'),
438
- name: 'file2.txt',
439
- extension: '.txt',
440
- size: 1000
441
- }
442
- ];
443
-
444
- // Mock fs.access to simulate that new file doesn't exist
445
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
446
- vi.mocked(fs.rename).mockResolvedValue(undefined);
447
-
448
- await fileRenamer.renameFiles(testFiles);
449
-
450
- const outputString = stdoutOutput.join('');
451
- expect(outputString).toContain('✅ Processed 2 files (2 successful)');
452
- expect(outputString).toContain('\n'); // Should end with newline
453
- });
454
-
455
- it('should truncate very long filenames in progress display', async () => {
456
- const longFilename = 'this-is-a-very-long-filename-that-should-be-truncated-for-better-display-purposes.txt';
457
- const testFiles: FileInfo[] = [
458
- {
459
- path: path.join(testDataDir, longFilename),
460
- name: longFilename,
461
- extension: '.txt',
462
- size: 1000
463
- }
464
- ];
465
-
466
- // Mock fs.access to simulate that new file doesn't exist
467
- vi.mocked(fs.access).mockRejectedValue({ code: 'ENOENT' });
468
- vi.mocked(fs.rename).mockResolvedValue(undefined);
469
-
470
- await fileRenamer.renameFiles(testFiles);
471
-
472
- const outputString = stdoutOutput.join('');
473
- expect(outputString).toContain('🔄 Processing [1/1] this-is-a-very-long-filename-that-should-be-tru...');
474
- expect(outputString).not.toContain(longFilename); // Full name should not appear
475
- });
476
-
477
- it('should handle mixed success/failure results in completion message', async () => {
478
- const testFiles: FileInfo[] = [
479
- {
480
- path: path.join(testDataDir, 'success.txt'),
481
- name: 'success.txt',
482
- extension: '.txt',
483
- size: 1000
484
- },
485
- {
486
- path: path.join(testDataDir, 'failure.txt'),
487
- name: 'failure.txt',
488
- extension: '.txt',
489
- size: 1000
490
- }
491
- ];
492
-
493
- // Mock the first file to succeed and second to fail
494
- vi.mocked(fs.access).mockImplementation((path: string) => {
495
- if (path.includes('success.txt')) {
496
- return Promise.reject({ code: 'ENOENT' }); // File doesn't exist, rename will succeed
497
- }
498
- return Promise.reject({ code: 'ENOENT' }); // File doesn't exist, rename will succeed
499
- });
500
-
501
- vi.mocked(fs.rename).mockImplementation((oldPath: string) => {
502
- if (oldPath.includes('failure.txt')) {
503
- return Promise.reject(new Error('Permission denied'));
504
- }
505
- return Promise.resolve(undefined);
506
- });
507
-
508
- await fileRenamer.renameFiles(testFiles);
509
-
510
- const outputString = stdoutOutput.join('');
511
- expect(outputString).toContain('✅ Processed 2 files (1 successful)');
512
- });
513
- });
514
- });