Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion src/__tests__/version-check.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { compareSemver, getUpdateType, checkForUpdates } from '../utils/version-check';
import { compareSemver, getUpdateType, checkForUpdates, getInstalledVersion } from '../utils/version-check';
import { logger } from '../utils/logger';
import { readFileSync } from 'node:fs';

vi.mock('node:fs', () => ({
readFileSync: vi.fn(() => JSON.stringify({ version: '1.2.2' })),
}));

vi.mock('../utils/logger', () => ({
logger: {
Expand Down Expand Up @@ -98,4 +103,68 @@ describe('version-check utilities', () => {
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('v9.9.9'));
});
});

describe('getInstalledVersion', () => {
afterEach(() => {
vi.mocked(readFileSync).mockReset();
});

it('should resolve and return the version if package.json in production path is valid', () => {
vi.mocked(readFileSync).mockImplementation((path) => {
if (typeof path === 'string' && path.endsWith('src/package.json')) {
return JSON.stringify({ version: '2.3.4' });
Comment on lines +113 to +115
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize mocked file paths before suffix checks.

Line 114/125/136/150 style checks assume / separators, so these tests can fail on Windows (\). Normalize path before endsWith() to make tests portable.

💡 Suggested fix
+const normalizePath = (p: unknown) => String(p).replace(/\\/g, '/');

     it('should resolve and return the version if package.json in production path is valid', () => {
       vi.mocked(readFileSync).mockImplementation((path) => {
-        if (typeof path === 'string' && path.endsWith('src/package.json')) {
+        const normalized = normalizePath(path);
+        if (normalized.endsWith('src/package.json')) {
           return JSON.stringify({ version: '2.3.4' });
         }
         throw new Error('File not found');
       });

       expect(getInstalledVersion()).toBe('2.3.4');
     });

Also applies to: 125-126, 136-140, 150-154

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/version-check.test.ts` around lines 113 - 115, The mocked
readFileSync implementation uses path.endsWith(...) assuming POSIX separators;
normalize the incoming path before the suffix checks to make tests OS-portable
by converting backslashes to forward slashes or using
path.normalize()/path.posix methods. Update the
vi.mocked(readFileSync).mockImplementation callback (and the other similar mock
implementations at the other locations) to coerce the path (e.g., const p =
String(path).replace(/\\\\/g, '/')) and then use p.endsWith('src/package.json')
(and the other suffixes) so the tests pass on Windows.

}
throw new Error('File not found');
});

expect(getInstalledVersion()).toBe('2.3.4');
});

it('should resolve and return the version if production path fails but development path succeeds', () => {
vi.mocked(readFileSync).mockImplementation((path) => {
if (typeof path === 'string' && path.endsWith('package.json') && !path.endsWith('src/package.json')) {
return JSON.stringify({ version: '1.2.3' });
}
throw new Error('File not found');
});

expect(getInstalledVersion()).toBe('1.2.3');
});

it('should advance to the next path if JSON is malformed', () => {
vi.mocked(readFileSync).mockImplementation((path) => {
if (typeof path === 'string' && path.endsWith('src/package.json')) {
return '{ malformed json';
}
if (typeof path === 'string' && path.endsWith('package.json') && !path.endsWith('src/package.json')) {
return JSON.stringify({ version: '1.2.9' });
}
throw new Error('File not found');
});

expect(getInstalledVersion()).toBe('1.2.9');
});

it('should advance to next path if version is not a string or missing', () => {
vi.mocked(readFileSync).mockImplementation((path) => {
if (typeof path === 'string' && path.endsWith('src/package.json')) {
return JSON.stringify({ version: 12345 }); // non-string version
}
if (typeof path === 'string' && path.endsWith('package.json') && !path.endsWith('src/package.json')) {
return JSON.stringify({ version: '1.2.8' });
}
throw new Error('File not found');
});

expect(getInstalledVersion()).toBe('1.2.8');
});

it('should fallback to 0.0.0 if both paths fail', () => {
vi.mocked(readFileSync).mockImplementation(() => {
throw new Error('Read failed');
});

expect(getInstalledVersion()).toBe('0.0.0');
});
});
});
20 changes: 13 additions & 7 deletions src/utils/version-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Read the actual package.json version at runtime
function getInstalledVersion(): string {
const pkgPath = join(__dirname, '..', '..', 'package.json');
try {
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
return pkg.version || '0.0.0';
} catch {
return '0.0.0';
export function getInstalledVersion(): string {
const paths = [
join(__dirname, '..', 'package.json'), // production: dist/../package.json
join(__dirname, '..', '..', 'package.json') // development: src/utils/../../package.json
];
for (const pkgPath of paths) {
try {
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
if (typeof pkg.version === 'string' && pkg.version) return pkg.version;
} catch {
// Try next path
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return '0.0.0';
}

// Compare two semver strings; returns 'lt' if a < b, 'gt' if a > b, 'eq' if equal
Expand Down
Loading