@amirdaraee/namewise 0.5.3 → 0.5.5

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 (83) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +60 -60
  3. package/dist/index.js +0 -0
  4. package/dist/services/claude-service.d.ts.map +1 -1
  5. package/dist/services/claude-service.js +3 -0
  6. package/dist/services/claude-service.js.map +1 -1
  7. package/dist/services/lmstudio-service.d.ts +1 -0
  8. package/dist/services/lmstudio-service.d.ts.map +1 -1
  9. package/dist/services/lmstudio-service.js +16 -1
  10. package/dist/services/lmstudio-service.js.map +1 -1
  11. package/dist/services/ollama-service.d.ts +1 -0
  12. package/dist/services/ollama-service.d.ts.map +1 -1
  13. package/dist/services/ollama-service.js +16 -1
  14. package/dist/services/ollama-service.js.map +1 -1
  15. package/dist/services/openai-service.d.ts.map +1 -1
  16. package/dist/services/openai-service.js +3 -0
  17. package/dist/services/openai-service.js.map +1 -1
  18. package/package.json +8 -8
  19. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -82
  20. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -61
  21. package/.github/workflows/auto-release.yml +0 -81
  22. package/.github/workflows/build.yml +0 -55
  23. package/.github/workflows/publish.yml +0 -134
  24. package/.github/workflows/test.yml +0 -45
  25. package/eng.traineddata +0 -0
  26. package/src/cli/commands.ts +0 -64
  27. package/src/cli/rename.ts +0 -171
  28. package/src/index.ts +0 -54
  29. package/src/parsers/excel-parser.ts +0 -66
  30. package/src/parsers/factory.ts +0 -38
  31. package/src/parsers/pdf-parser.ts +0 -99
  32. package/src/parsers/text-parser.ts +0 -43
  33. package/src/parsers/word-parser.ts +0 -50
  34. package/src/services/ai-factory.ts +0 -39
  35. package/src/services/claude-service.ts +0 -119
  36. package/src/services/file-renamer.ts +0 -141
  37. package/src/services/lmstudio-service.ts +0 -161
  38. package/src/services/ollama-service.ts +0 -191
  39. package/src/services/openai-service.ts +0 -117
  40. package/src/types/index.ts +0 -76
  41. package/src/types/pdf-extraction.d.ts +0 -7
  42. package/src/utils/ai-prompts.ts +0 -76
  43. package/src/utils/file-templates.ts +0 -275
  44. package/src/utils/naming-conventions.ts +0 -67
  45. package/src/utils/pdf-to-image.ts +0 -137
  46. package/tests/data/console-test-1.txt +0 -1
  47. package/tests/data/console-test-2.txt +0 -1
  48. package/tests/data/console-test-long-filename-for-display-testing.txt +0 -1
  49. package/tests/data/empty-file.txt +0 -0
  50. package/tests/data/failure.txt +0 -1
  51. package/tests/data/file1.txt +0 -1
  52. package/tests/data/file2.txt +0 -1
  53. package/tests/data/much-longer-filename-to-test-clearing.txt +0 -1
  54. package/tests/data/sample-markdown.md +0 -9
  55. package/tests/data/sample-pdf.pdf +0 -0
  56. package/tests/data/sample-text.txt +0 -25
  57. package/tests/data/short.txt +0 -1
  58. package/tests/data/single-file.txt +0 -1
  59. package/tests/data/success.txt +0 -1
  60. package/tests/data/this-is-a-very-long-filename-that-should-be-truncated-for-better-display-purposes.txt +0 -1
  61. package/tests/data/very-long-filename-that-should-be-cleared-properly.txt +0 -1
  62. package/tests/data/x.txt +0 -1
  63. package/tests/integration/ai-prompting.test.ts +0 -386
  64. package/tests/integration/end-to-end.test.ts +0 -209
  65. package/tests/integration/person-name-extraction.test.ts +0 -440
  66. package/tests/integration/workflow.test.ts +0 -336
  67. package/tests/mocks/mock-ai-service.ts +0 -58
  68. package/tests/unit/cli/commands.test.ts +0 -169
  69. package/tests/unit/parsers/factory.test.ts +0 -100
  70. package/tests/unit/parsers/pdf-parser.test.ts +0 -63
  71. package/tests/unit/parsers/text-parser.test.ts +0 -85
  72. package/tests/unit/services/ai-factory.test.ts +0 -85
  73. package/tests/unit/services/claude-service.test.ts +0 -188
  74. package/tests/unit/services/file-renamer.test.ts +0 -514
  75. package/tests/unit/services/lmstudio-service.test.ts +0 -326
  76. package/tests/unit/services/ollama-service.test.ts +0 -264
  77. package/tests/unit/services/openai-service.test.ts +0 -196
  78. package/tests/unit/utils/ai-prompts.test.ts +0 -213
  79. package/tests/unit/utils/file-templates.test.ts +0 -199
  80. package/tests/unit/utils/naming-conventions.test.ts +0 -88
  81. package/tests/unit/utils/pdf-to-image.test.ts +0 -127
  82. package/tsconfig.json +0 -20
  83. 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
- });