Skip to content

Commit 4d3b226

Browse files
Merge pull request #6 from PatchPulse/cater-for-x-versions
Cater for x versions (i.e. version ranges)
2 parents 959f42a + e273c9c commit 4d3b226

File tree

14 files changed

+217
-113
lines changed

14 files changed

+217
-113
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
coverage
22
lib
33
node_modules
4+
patchpulse.config.json
45
STREAMING_TODO.md

.patchpulse.config.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
lib
22
node_modules
3-
.patchpulse.config.json.example
3+
patchpulse.config.json.example

package-lock.json

Lines changed: 42 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "patch-pulse",
3-
"version": "2.6.1",
3+
"version": "2.7.0",
44
"description": "Check for outdated npm dependencies",
55
"type": "module",
66
"bin": {
@@ -58,7 +58,7 @@
5858
},
5959
"devDependencies": {
6060
"@eslint/js": "9.29.0",
61-
"@types/node": "24.0.3",
61+
"@types/node": "24.0.4",
6262
"@typescript-eslint/eslint-plugin": "8.35.0",
6363
"@typescript-eslint/parser": "8.35.0",
6464
"@vitest/coverage-v8": "3.2.4",
File renamed without changes.

src/core/dependency-checker.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,29 @@ export async function checkDependencyVersions(
2929

3030
for (let i = 0; i < packageNames.length; i += concurrencyLimit) {
3131
const batch = packageNames.slice(i, i + concurrencyLimit);
32-
const batchPromises = batch.map(async name => {
33-
const version = dependencies[name];
32+
const batchPromises = batch.map(async packageName => {
33+
const version = dependencies[packageName];
3434

35-
// Check if package should be skipped
36-
const isSkipped = config ? shouldSkipPackage(name, config) : false;
35+
const isSkipped = shouldSkipPackage({ packageName, config });
3736

3837
let latestVersion: string | undefined;
3938
let isOutdated = false;
4039
let updateType: 'patch' | 'minor' | 'major' | undefined;
4140

4241
if (!isSkipped) {
43-
latestVersion = await getLatestVersion(name);
44-
isOutdated = latestVersion
45-
? isVersionOutdated(version, latestVersion)
46-
: false;
47-
updateType =
48-
latestVersion && isOutdated
42+
latestVersion = await getLatestVersion(packageName);
43+
44+
// Don't try to compare versions if current version is not a standard semver
45+
const isStandardSemver = /^\d+\.\d+\.\d+/.test(version);
46+
if (!isStandardSemver) {
47+
isOutdated = false;
48+
updateType = undefined;
49+
} else if (latestVersion) {
50+
isOutdated = isVersionOutdated(version, latestVersion);
51+
updateType = isOutdated
4952
? getUpdateType(version, latestVersion)
5053
: undefined;
54+
}
5155
}
5256

5357
// Update progress for each completed package
@@ -57,7 +61,7 @@ export async function checkDependencyVersions(
5761
);
5862

5963
return {
60-
name,
64+
packageName,
6165
currentVersion: version,
6266
latestVersion,
6367
isOutdated,
@@ -88,6 +92,12 @@ function displayResults(dependencyInfos: DependencyInfo[]): void {
8892
} else if (!dep.latestVersion) {
8993
status = chalk.red('NOT FOUND');
9094
versionInfo = `${dep.currentVersion} (not found on npm registry)`;
95+
} else if (dep.currentVersion === 'latest') {
96+
status = chalk.cyan('LATEST TAG');
97+
versionInfo = `latest → ${chalk.cyan(dep.latestVersion)} (actual latest version)`;
98+
} else if (!/^\d+\.\d+\.\d+/.test(dep.currentVersion)) {
99+
status = chalk.blue('VERSION RANGE');
100+
versionInfo = `${dep.currentVersion}${chalk.cyan(dep.latestVersion)} (latest available)`;
91101
} else if (dep.isOutdated) {
92102
const updateTypeColor = {
93103
major: chalk.yellow,
@@ -102,7 +112,7 @@ function displayResults(dependencyInfos: DependencyInfo[]): void {
102112
}
103113

104114
console.log(
105-
`${status} ${chalk.white(dep.name)} ${chalk.gray(versionInfo)}`
115+
`${status} ${chalk.white(dep.packageName)} ${chalk.gray(versionInfo)}`
106116
);
107117
}
108118
}

src/gen/version.gen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// Auto-generated file - do not edit manually
2-
export const VERSION = '2.6.1';
2+
export const VERSION = '2.7.0';

src/index.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
import chalk from 'chalk';
44
import { join } from 'path';
55
import { checkDependencyVersions } from './core/dependency-checker';
6-
import {
7-
mergeConfigs,
8-
parseCliConfig,
9-
readConfigFile,
10-
} from './services/config';
6+
import { getConfig } from './services/config';
117
import { checkForCliUpdate } from './services/npm';
128
import { readPackageJson } from './services/package';
139
import { type DependencyInfo } from './types';
@@ -32,10 +28,7 @@ async function main(): Promise<void> {
3228
const packageJson = await readPackageJson(packageJsonPath);
3329
const allDependencies: DependencyInfo[] = [];
3430

35-
// Read configuration
36-
const fileConfig = readConfigFile();
37-
const cliConfig = parseCliConfig(process.argv.slice(2));
38-
const config = mergeConfigs(fileConfig, cliConfig);
31+
const config = getConfig();
3932

4033
const dependencyTypeLabels: Record<string, string> = {
4134
dependencies: 'Dependencies',

src/services/__tests__/config.test.ts

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'fs';
22
import { join } from 'path';
33
import { beforeEach, describe, expect, it, vi } from 'vitest';
44
import {
5+
getConfig,
56
mergeConfigs,
67
parseCliConfig,
78
readConfigFile,
@@ -23,10 +24,17 @@ describe('Configuration Service', () => {
2324
vi.clearAllMocks();
2425
});
2526

27+
describe('getConfig', () => {
28+
it('should return the config', () => {
29+
const config = getConfig();
30+
expect(config).toEqual({ skip: [] });
31+
});
32+
});
33+
2634
describe('readConfigFile', () => {
2735
it('should return null when no config file exists', () => {
2836
vi.mocked(existsSync).mockReturnValue(false);
29-
vi.mocked(join).mockReturnValue('/test/.patchpulse.config.json');
37+
vi.mocked(join).mockReturnValue('/test/patchpulse.config.json');
3038

3139
const result = readConfigFile('/test');
3240

@@ -114,7 +122,7 @@ describe('Configuration Service', () => {
114122
const result = mergeConfigs(fileConfig, cliConfig);
115123

116124
expect(result).toEqual({
117-
skip: ['express', 'test-*'],
125+
skip: ['lodash', '@types/*', 'express', 'test-*'],
118126
});
119127
});
120128

@@ -149,50 +157,89 @@ describe('Configuration Service', () => {
149157
skip: ['lodash', 'express'],
150158
};
151159

152-
expect(shouldSkipPackage('lodash', config)).toBe(true);
153-
expect(shouldSkipPackage('express', config)).toBe(true);
154-
expect(shouldSkipPackage('chalk', config)).toBe(false);
160+
expect(shouldSkipPackage({ packageName: 'lodash', config })).toBe(true);
161+
expect(shouldSkipPackage({ packageName: 'express', config })).toBe(true);
162+
expect(shouldSkipPackage({ packageName: 'chalk', config })).toBe(false);
155163
});
156164

157165
it('should skip packages matching patterns', () => {
158166
const config: PatchPulseConfig = {
159167
skip: ['@types/*', 'test-*'],
160168
};
161169

162-
expect(shouldSkipPackage('@types/node', config)).toBe(true);
163-
expect(shouldSkipPackage('test-utils', config)).toBe(true);
164-
expect(shouldSkipPackage('@typescript-eslint/parser', config)).toBe(
165-
false
166-
);
167-
expect(shouldSkipPackage('chalk', config)).toBe(false);
170+
expect(
171+
shouldSkipPackage({
172+
packageName: '@types/node',
173+
config,
174+
})
175+
).toBe(true);
176+
expect(
177+
shouldSkipPackage({
178+
packageName: 'test-utils',
179+
config,
180+
})
181+
).toBe(true);
182+
expect(
183+
shouldSkipPackage({
184+
packageName: '@typescript-eslint/parser',
185+
config,
186+
})
187+
).toBe(false);
188+
expect(shouldSkipPackage({ packageName: 'chalk', config })).toBe(false);
168189
});
169190

170191
it('should handle invalid regex patterns gracefully', () => {
171192
const config: PatchPulseConfig = {
172193
skip: ['[invalid-regex'],
173194
};
174195

175-
expect(shouldSkipPackage('test-package', config)).toBe(false);
196+
expect(
197+
shouldSkipPackage({
198+
packageName: 'test-package',
199+
config,
200+
})
201+
).toBe(false);
176202
});
177203

178204
it('should check both exact matches and patterns', () => {
179205
const config: PatchPulseConfig = {
180206
skip: ['lodash', '@types/*'],
181207
};
182208

183-
expect(shouldSkipPackage('lodash', config)).toBe(true);
184-
expect(shouldSkipPackage('@types/node', config)).toBe(true);
185-
expect(shouldSkipPackage('chalk', config)).toBe(false);
209+
expect(shouldSkipPackage({ packageName: 'lodash', config })).toBe(true);
210+
expect(
211+
shouldSkipPackage({
212+
packageName: '@types/node',
213+
config,
214+
})
215+
).toBe(true);
216+
expect(shouldSkipPackage({ packageName: 'chalk', config })).toBe(false);
186217
});
187218

188219
it('should treat patterns without regex chars as exact matches', () => {
189220
const config: PatchPulseConfig = {
190221
skip: ['lodash', 'test-package'],
191222
};
192223

193-
expect(shouldSkipPackage('lodash', config)).toBe(true);
194-
expect(shouldSkipPackage('test-package', config)).toBe(true);
195-
expect(shouldSkipPackage('test-package-extra', config)).toBe(false);
224+
expect(shouldSkipPackage({ packageName: 'lodash', config })).toBe(true);
225+
expect(
226+
shouldSkipPackage({
227+
packageName: 'test-package',
228+
config,
229+
})
230+
).toBe(true);
231+
expect(
232+
shouldSkipPackage({
233+
packageName: 'test-package-extra',
234+
config,
235+
})
236+
).toBe(false);
196237
});
197238
});
239+
240+
it('should handle no defined config skip parameter', () => {
241+
expect(shouldSkipPackage({ packageName: 'lodash', config: {} })).toBe(
242+
false
243+
);
244+
});
198245
});

0 commit comments

Comments
 (0)